//          _/             _/_/
//        _/_/           _/_/_/
//      _/_/_/_/         _/_/_/
//      _/_/_/_/_/       _/_/_/              ____________________________________________ 
//      _/_/_/_/_/       _/_/_/             /                                           / 
//      _/_/_/_/_/       _/_/_/            /                                 28F00AP30 / 
//      _/_/_/_/_/       _/_/_/           /                                           /  
//      _/_/_/_/_/_/     _/_/_/          /                                     1Gbit / 
//      _/_/_/_/_/_/     _/_/_/         /                                single die / 
//      _/_/_/ _/_/_/    _/_/_/        /                                           / 
//      _/_/_/  _/_/_/   _/_/_/       /                  Verilog Behavioral Model / 
//      _/_/_/   _/_/_/  _/_/_/      /                               Version 1.7 / 
//      _/_/_/    _/_/_/ _/_/_/     /                                           /
//      _/_/_/     _/_/_/_/_/_/    /           Copyright (c) 2010 Numonyx B.V. / 
//      _/_/_/      _/_/_/_/_/    /___________________________________________/ 
//      _/_/_/       _/_/_/_/      
//      _/_/          _/_/_/  
// 
//     
//             NUMONYX              
`include "include/def.h"
`include "include/CUIcommandData.h"
`include "include/UserData.h"
`include "include/data.h"
`include "include/BankLib.h"
`include "include/TimingData.h"

// **********************************
//
// Timing Lib Module :
// 
//      checks all timing constraints            
//
// **********************************

module TimingLibModule(A, DQ, W_N, G_N, E_N, L_N, WP_N, CK, VPP);  

input [`ADDRBUS_dim-1:0] A;           // Address Bus 
input [`DATABUS_dim-1:0] DQ;          // Data I/0 Bus

input W_N, G_N, E_N, L_N, WP_N, CK;
input [`Voltage_range] VPP;

integer AddValid_time;
integer AddNotValid_time;
integer DataValid_time;
integer DataXX_time;

integer WriteEnableLow_time;
integer WriteEnableHigh_time;
integer OutputEnableLow_time;
integer OutputEnableHigh_time;
integer LatchEnableHigh_time;
integer LatchEnableLow_time;
integer ChipEnableLow_time; 
integer ChipEnableHigh_time;
integer RisingEdge_time;
integer FallingEdge_time;

integer WaitValid_time;

integer WriteProtectHigh_time;
integer WriteProtectLow_time;
integer VPPSupplyHigh_time;
integer VPPSupplyLow_time;

reg afterReset;

reg isValid;
reg dataValid;
reg addressValid;
reg reading;
reg writing;
reg dataXX;
time temp;


initial begin

        AddValid_time         = 0;
        AddNotValid_time      = 0;
        DataValid_time        = 0;
        DataXX_time           = 0;

        WriteEnableLow_time   = 0;
        WriteEnableHigh_time  = 0;
        OutputEnableLow_time  = 0;
        OutputEnableHigh_time = 0;
        LatchEnableHigh_time  = 0;
        LatchEnableLow_time   = 0;
        ChipEnableLow_time    = 0;
        ChipEnableHigh_time   = 0;

        WaitValid_time        = 0;

        WriteProtectHigh_time = 0;
        WriteProtectLow_time  = 0;

        RisingEdge_time  = 0;
        FallingEdge_time = 0;

        dataValid = `FALSE;
        dataXX    = `FALSE;
        addressValid = `FALSE;

        reading = `FALSE;
        writing = `FALSE;

        afterReset = `FALSE;

  end


// **************
//
// Change address
//
// **************
        
always @(A) begin : AddressCheck

        if (`TimingChecks == "off") 
                        disable AddressCheck;

        if ($time > `Reset_time)
        begin
        if (isAddValid(A))               // Address Valid
                begin
                        temp = $time - AddValid_time;
                        checkTiming("tAVAV", TimingData_man.tAVAV, temp, "min");

                        temp = $time - WriteEnableHigh_time;
                        checkTiming("tWHAV", TimingData_man.tWHAV, temp, "min");

                        AddValid_time = $time;        
                        addressValid = `TRUE;
                end

                else
                begin
                        if (isAddXX(A) || isAddZZ(A))             // Address XXXX o ZZZZ
                                begin
                                
                                        if (addressValid)
                                        begin
                                                temp = $time - LatchEnableHigh_time;
                                                checkTiming("tLHAX", TimingData_man.tLHAX, temp, "min");

                                                temp = $time - WriteEnableHigh_time;
                                                checkTiming("tWHAX", TimingData_man.tWHAX, temp, "min");

                                            
                                                AddNotValid_time = $time;        
                                        end
                                        addressValid = `FALSE;
                                end
                 end
      end           
end


// ***********
//
// Change data
//
// ***********

always @(DQ) begin : DataCheck

   if (`TimingChecks == "off") 
                        disable DataCheck;

   if ($time > `Reset_time)
        begin

   if (isDataValid(DQ))               // Data Valid
        begin
                if (ConfigReg_man.isSynchronous)               // Synchronous mode 

                        begin
                           if (reading)
                                if (ConfigReg_man.isRisingClockEdge)
                                        begin
                                                temp = $time - RisingEdge_time;
                                        end
                                else
                                        begin
                                                temp = $time - FallingEdge_time;

                                        end
                        end

                else         // Asynchronous mode
                begin        
                
                        temp = $time - AddValid_time;

                        temp = $time - LatchEnableLow_time;

                        temp = $time - ChipEnableLow_time;

                        if (reading)
                                begin
                                        temp = $time - OutputEnableLow_time;
                                        
                                        temp = $time - WriteEnableHigh_time;
                                end

                        DataValid_time = $time;   
                        dataValid = `TRUE;
                        dataXX = `FALSE;
                end
       end         
                        
    else
        begin
                if (isDataXX(DQ))                 // Data XXXX
                        begin
                                if (dataValid) 
                                        begin
                                                temp = $time - AddNotValid_time;

                                                temp = $time - ChipEnableHigh_time;
                                                checkTiming("tEHQX", TimingData_man.tEHQX, temp, "min");

                                                temp = $time - OutputEnableHigh_time;

                                        end

                                 else    
                                        begin
                                        
                                                temp = $time - ChipEnableLow_time;
                                                checkTiming("tELQX", TimingData_man.tELQX, temp, "min");

                                                if (reading)
                                                        begin
                                                                temp = $time - OutputEnableLow_time;
                                                                checkTiming("tGLQX", TimingData_man.tGLQX, temp, "min");
                                                        end        
                                        end


                                  DataXX_time = $time;
                                  dataValid = `FALSE;
                                  dataXX = `TRUE;
                                
                           end

                   else 
                        if (isDataZZ(DQ))        
                                begin
                                        if (dataXX) 
                                                begin
                                                        temp = $time - ChipEnableHigh_time;
                                                        checkTiming("tEHQZ", TimingData_man.tEHQZ, temp, "max");
                                                        temp = $time - OutputEnableHigh_time;
                                                        checkTiming("tGHQZ", TimingData_man.tGHQZ, temp, "max");
                                                end

                                        if (dataValid)
                                                begin
                                                        temp = $time - WriteEnableHigh_time;
                                                        checkTiming("tWHDX", TimingData_man.tWHDX, temp, "min");
   
                                                end
                                        
                                        dataValid = `FALSE;
                                        dataXX  = `FALSE;
                                 end
           end
    end       
end           
               
// ******************
//
// Change Chip Enable
//
// ******************

 always @(posedge E_N) begin : ChipHighCheck    // Chip Enable High

 if (`TimingChecks == "off") 
                        disable ChipHighCheck;

 if ($time > `Reset_time)
        begin
      
      temp = $time - WriteEnableHigh_time;
      checkTiming("tWHEH", TimingData_man.tWHEH, temp, "min");

      ChipEnableHigh_time = $time;
   end
 end

always @(negedge E_N) begin : ChipLowCheck    // Chip Enable Low

if (`TimingChecks == "off") 
                        disable ChipLowCheck;

  if ($time > `Reset_time)
        begin
                                
      ChipEnableLow_time = $time;
  end    

end

always @(posedge L_N) begin : LatchLowCheck    // Latch Enable High

if (`TimingChecks == "off") 
                        disable LatchLowCheck;

   if ($time > `Reset_time)
        begin
                                
       temp = $time - AddValid_time;
       checkTiming("tAVLH", TimingData_man.tAVLH, temp, "min");
                              
       temp = $time - LatchEnableLow_time;
       checkTiming("tLLLH", TimingData_man.tLLLH, temp, "min");
                                
       temp = $time - ChipEnableLow_time;
       checkTiming("tELLH", TimingData_man.tELLH, temp, "min");

       LatchEnableHigh_time = $time;
  end
end

always @(negedge L_N)  begin : LatchHighCheck  // Latch Enable Low

if (`TimingChecks == "off") 
                        disable LatchHighCheck;

if ($time > `Reset_time)
        begin
                                
       temp = $time - WriteEnableHigh_time;
       checkTiming("tWHLL", TimingData_man.tWHLL, temp, "min");

       temp = $time - RisingEdge_time;
       checkTiming("tCHVL", TimingData_man.tCHVL, temp, "min");

       LatchEnableLow_time = $time;
  end

end



always  @(posedge G_N) begin : OutputHighCheck    // Output Enable High

if (`TimingChecks == "off") 
                        disable OutputHighCheck;

  if ($time > `Reset_time)
        begin
                                
      OutputEnableHigh_time = $time;
      reading = `FALSE;
  end

end

always @(negedge G_N) begin : OutputLowCheck    // Output Enable Low

if (`TimingChecks == "off") 
                        disable OutputLowCheck;

    if ($time > `Reset_time)
        begin

                                
       temp = $time - LatchEnableHigh_time;

       temp = $time - WriteEnableHigh_time;
       checkTiming("tWHGL", TimingData_man.tWHGL, temp, "min");


       OutputEnableLow_time = $time;
       reading = `TRUE;
   end    

end
                        
always @(posedge W_N) begin : WriteHighCheck    // Write Enable High

if (`TimingChecks == "off") 
                        disable WriteHighCheck;

   if ($time > `Reset_time)
        begin

                                
       temp = $time - AddValid_time;
       checkTiming("tAVWH", TimingData_man.tAVWH, temp, "min");

       if (writing)
          begin
                temp = $time - WriteEnableLow_time;
                checkTiming("tWLWH", TimingData_man.tWLWH, temp, "min");
          end        
                                
       temp = $time - DataValid_time;
       checkTiming("tDVWH", TimingData_man.tDVWH, temp, "min");

       temp = $time - WriteProtectHigh_time;
       checkTiming("tWPHWH", TimingData_man.tWPHWH, temp, "min");

       temp = $time - VPPSupplyHigh_time;
       checkTiming("tVPHWH", TimingData_man.tVPHWH, temp, "min");

       WriteEnableHigh_time = $time;
       writing = `FALSE;
   end
end
                
always @(negedge W_N) begin : WriteLowCheck    // Write Enable Low

if (`TimingChecks == "off") 
                        disable WriteLowCheck;

  if ($time > `Reset_time)
        begin
                                
       temp = $time - ChipEnableLow_time;
       checkTiming("tELWL", TimingData_man.tELWL, temp, "min");

       temp = $time - WriteEnableHigh_time;
       checkTiming("tWHWL", TimingData_man.tWHWL, temp, "min");

       WriteEnableLow_time = $time;
       writing = `TRUE; 
   end
end

always  @(posedge WP_N) begin : WPHighCheck            // Write Protect High

if (`TimingChecks == "off") 
                        disable WPHighCheck;

if ($time > `Reset_time)
        begin
                               
    WriteProtectHigh_time = $time; 
 end   
end

always  @(negedge WP_N) begin : WPLowCheck               // Write Protect Low

if (`TimingChecks == "off") 
                        disable WPLowCheck;

  if ($time > `Reset_time)
        begin
                                
    temp = $time - DataValid_time;
    checkTiming("tQVWPL", TimingData_man.tQVWPL, temp, "min");

    WriteProtectLow_time = $time;
 end   
end

always @(posedge VPP) begin : VPPHighCheck            // Write Protect High

if (`TimingChecks == "off") 
                        disable VPPHighCheck;

  if ($time > `Reset_time)
        begin
                               
       VPPSupplyHigh_time = $time; 
  end     
end

always @(negedge VPP) begin : VPPLowCheck               // Write Protect Low

if (`TimingChecks == "off") 
                        disable VPPLowCheck;

if ($time > `Reset_time)
        begin
                                
      temp = $time - DataValid_time;
      checkTiming("tQVVPL", TimingData_man.tQVVPL, temp, "min");

      VPPSupplyLow_time = $time;

   end   
end

always @(posedge CK) begin : RisingCKCheck                // Write Protect Low

if (`TimingChecks == "off") 
                        disable RisingCKCheck;

if ($time > `Reset_time)
   begin
       
        temp = $time - LatchEnableLow_time;
        checkTiming("tVLCH", TimingData_man.tLLKH, temp, "min");

        temp = $time - ChipEnableLow_time;
        checkTiming("tELCH", TimingData_man.tELKH, temp, "min");

        RisingEdge_time = $time;
   end   
end

always @(negedge CK) begin : FallingCKCheck                // Write Protect Low

if (`TimingChecks == "off") 
                        disable FallingCKCheck;

if ($time > `Reset_time)
   begin
                                
        FallingEdge_time = $time;
      
   end   
end



// **********************************************
//
// FUNCTION isAddValid :
//      return true if the input address is valid
//
// **********************************************

function isAddValid;

input [`ADDRBUS_dim - 1 : 0] Add;

reg [`ADDRBUS_dim - 1 : 0] Add;

reg valid;
integer count;

begin

        valid = `TRUE;
        begin : cycle
                for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
                        begin
                                if ((Add[count] !== 1'b0) && (Add[count] !== 1'b1))
                                        begin
                                                valid = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isAddValid = valid;
end
endfunction


// *********************************************
//
// FUNCTION isAddXX :
//      return true if the input address is XXXX
//
// *********************************************

function isAddXX;

input [`ADDRBUS_dim - 1 : 0] Add;

reg [`ADDRBUS_dim - 1 : 0] Add;

reg allxx;
integer count;

begin

        allxx = `TRUE;
        begin : cycle
                for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
                        begin
                                if (Add[count] !== 1'bx)
                                        begin
                                                allxx = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isAddXX = allxx;
end
endfunction

// *********************************************
//
// FUNCTION isAddZZ :
//      return true if the input address is ZZZZ
//
// *********************************************

function isAddZZ;

input [`ADDRBUS_dim - 1 : 0] Add;

reg [`ADDRBUS_dim - 1 : 0] Add;

reg allzz;
integer count;

begin

        allzz = `TRUE;
        begin : cycle
                for (count = 0; count <= `ADDRBUS_dim - 1; count = count + 1)
                        begin
                                if (Add[count] !== 1'bz)
                                        begin
                                                allzz = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isAddZZ = allzz;
end
endfunction

// **********************************************
//
// FUNCTION isDataValid :
//      return true if the data is valid
//
// **********************************************

function isDataValid;

input [`DATABUS_dim - 1 : 0] Data;

reg [`DATABUS_dim - 1 : 0] Data;

reg valid;
integer count;

begin

        valid = `TRUE;
        begin : cycle
                for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
                        begin
                                if ((Data[count] !== 1'b0) && (Data[count] !== 1'b1))
                                        begin
                                                valid = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isDataValid = valid;
end
endfunction

// ***************************************
//
// FUNCTION isDataXX :
//      return true if the data is unknown
//
// ***************************************

function isDataXX;

input [`DATABUS_dim - 1 : 0] Data;

reg [`DATABUS_dim - 1 : 0] Data;

reg allxx;
integer count;

begin

        allxx = `TRUE;
        begin : cycle
                for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
                        begin
                                if (Data[count] !== 1'bx) 
                                        begin
                                                allxx = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isDataXX = allxx;
end
endfunction

// ************************************
//
// FUNCTION isDataZZ :
//      return true if the data is Hi-Z
//
// ************************************

function isDataZZ;

input [`DATABUS_dim - 1 : 0] Data;

reg [`DATABUS_dim - 1 : 0] Data;

reg allzz;
integer count;

begin

        allzz = `TRUE;
        begin : cycle
                for (count = 0; count <= `DATABUS_dim - 1; count = count + 1)
                        begin
                                if (Data[count] !== 1'bz) 
                                        begin
                                                allzz = `FALSE;
                                                disable cycle;
                                        end
                        end
        end                
        
        isDataZZ = allzz;
end
endfunction

// *****************************
//
// Task Check Timing
//      check timing constraints
//
// *****************************

task checkTiming;
  input [8*6:1] tstr;
  input [31:0]  tOK, tcheck;
  input [8*3:1] check_str;
  
  begin
        if ((check_str == "min") && (tcheck < tOK)) begin
                $display ("[%t]  !ERROR: %0s timing constraint violation!! ", $time, tstr);
        end         

        else 
                if ((check_str == "max") && (tcheck > tOK))
                        $display ("[%t]  !ERROR: %0s timing constraint violation!! ", $time, tstr);
  end
  endtask


endmodule

// Protect Manager
// implements the architecture of the memory blocks

module BlockLockModule(address, WP_N, RP_N, Info);




  input [`ADDRBUS_range] address;
  input WP_N, RP_N;
  input Info;

  reg LockArray [`BLOCK_dim - 1 : 0]; 
  reg LockDownArray [`BLOCK_dim - 1 : 0];  

  reg [`BYTE_range] Status;
  integer count;
  initial begin              // constructor sequence         
  
       for (count = 0; count <= `BLOCK_dim - 1; count = count + 1)             // all blocks are locked at power-up
                begin 
                LockDownArray[count] = `UNLOCKDOWN;
                if (`BLOCKPROTECT == "on") LockArray[count] = `LOCK;
                                      else LockArray[count] = `UNLOCK;
                end
  end
  
  always @(negedge RP_N) begin
                       initLockArray;
  end
  
  task initLockArray;
    begin 
  
    for (count = 0; count <= `BLOCK_dim - 1; count = count + 1)             // all blocks are locked at power-up
                begin 
                LockDownArray[count] = `UNLOCKDOWN;
                LockArray[count] = `LOCK;
                end
     end
  endtask

 
  
  // ********************************************
  //
  // FUNCTION isLocked : return the status of the 
  //                     specified block 
  //
  // ********************************************

  function IsLocked;                     // boolean function primitive   
  
  input [`ADDRBUS_range] address;
 
  integer n_block;

  begin
  
    n_block  = BankLib_man.getBlock(address); 
    IsLocked = (LockArray[n_block] == `LOCK) ? `TRUE : `FALSE;
  
  end
  endfunction  

  // ********************************************
  //
  // FUNCTION isLocked : return the status of the 
  //                     specified block 
  //
  // ********************************************

  function IsUnLocked;                     // boolean function primitive   
  
  input [`ADDRBUS_range] address;
  integer n_block;

  begin
  
    n_block  = BankLib_man.getBlock(address); 
    IsUnLocked = (LockArray[n_block] == `UNLOCK) ? `TRUE : `FALSE;
  
  end
  endfunction  


  function getLockBit;                     // boolean function primitive   
  
  input [`ADDRBUS_range] address;
  integer n_block;

  begin
  
    n_block  = BankLib_man.getBlock(address); 
    getLockBit = LockArray[n_block];
  
  end
  endfunction  

  function getLockDownBit;                     // boolean function primitive   
  
  input [`ADDRBUS_range] address;
  integer n_block;

  begin
  
    n_block  = BankLib_man.getBlock(address); 
    getLockDownBit = LockDownArray[n_block];
  
  end
  endfunction  

 
  // ********************************
  //
  // Task UnLock :
  //    implements Block UnLock Command
  //
  // ********************************

  task UnLock;
  
  output [`BYTE_range] Status;
  reg [`BYTE_range] Status;

  integer n_block;
  
  begin
  
        n_block = BankLib_man.getBlock(address);
        Status = `NoError_msg;
        if (LockDownArray[n_block]==`LOCKDOWN && WP_N==`LOW) Status = `NoUnLock_msg;
                                              else  LockArray[n_block] = `UNLOCK;
  end
  endtask

  // ********************************
  //
  // Task Lock :
  //    implements Block Lock Command
  //
  // ********************************

  task Lock;
  
  output [`BYTE_range] Status;
  reg [`BYTE_range]    Status;

  integer n_block;
  
  begin
  
        n_block = BankLib_man.getBlock(address);
        Status = `NoError_msg;
        LockArray[n_block] = `LOCK;
  end
  endtask

  // *****************************************
  //
  // Task LockDown :
  //    implements the Block Lock-Down Command
  //
  // *****************************************

  task LockDown;
  
  output [`BYTE_range] Status;
  reg [`BYTE_range] Status;

  integer n_block;
  
    begin
  
        n_block = BankLib_man.getBlock(address);
        Status = `NoError_msg;
        LockDownArray[n_block] = `LOCKDOWN;
        
    end 
  endtask


endmodule 

// *************************
//
// CFI Query Module
// Implements the CFI memory
//
// *************************

module CFIqueryModule();   //, isCFI);

//input isCFI;

reg [`BYTE_range] CFIarray [0:`CFI_dim];
reg error;
reg [8*40:1] CFI_file;
integer i;
  
initial begin
              
              `ifdef x256P30T 

               CFI_file = "CFImemory_top.vmf";
               `elsif x256P30B
               CFI_file= "CFImemory_bottom.vmf";
               `elsif x512P30T
               CFI_file = "CFImemory512Mb_top.vmf";
               `elsif x512P30B 
               CFI_file= "CFImemory512Mb_bottom.vmf";
               `elsif x512P30E 
                CFI_file= "CFImemory512Mb_symmetrical.vmf";
               `elsif x00AP30T
               CFI_file = "CFImemory1Gb_top.vmf";
               `elsif x00AP30B 
               CFI_file= "CFImemory1Gb_bottom.vmf";
               `elsif x00AP30E 
               CFI_file= "CFImemory1Gb_symmetrical.vmf";
               `endif
               
               for (i=0; i <= `CFI_dim; i = i + 1) CFIarray[i] = {8{`HIGH}};   // CFI Memory Init
               $readmemb(CFI_file,CFIarray);
end 

always @(posedge error)  begin
        Kernel.SetWarning(`RCFI_cmd,16'h00,`CFIAddrRange_msg);
        error = `FALSE;
end
  
function [`WORD_range] Get;

input [`ADDRBUS_range] address;
  
    begin
        if (address[`BYTE_range] >= 9'h10 && address[`BYTE_range] <= `CFI_dim )  //verificare se tener conto che il primo indirizzo accessibile e' 10h
                begin 
                if (address[`BYTE_range] >= 9'h39 && address[`BYTE_range] <= 9'h109) begin
                        Get = 8'hXX;
                        error = `TRUE;
                end else begin

                        Get[`LOW_range] = CFIarray[address[`BYTE_range]];
                        Get[`HIGH_range] = 8'h00;
                end
                
        end else 
                begin
                        Get = 8'hXX;
                        error = `TRUE;
                end
    end
endfunction
    
endmodule


// ********************************
//
// Data Error Module
// 
//      search for errors in data.h
//
// ********************************

module DataErrorModule; 




reg SevError;

initial begin

        SevError = `FALSE;






         
        if ((`BLOCKPROTECT != "on") && (`BLOCKPROTECT != "off")) 
                begin
                        SevError = `TRUE;
                        $display("!Error: BLOCK PROTECT INVALID: it must be on or off!!!");
                end

        if ((`TimingChecks != "on") && (`TimingChecks != "off")) 
                begin
                        SevError = `TRUE;
                        $display("!Error: TIMING CHECKS INVALID: it must be on or off!!!");
                end


        if ((`t_access != 100) && (`t_access != 110))
                begin
                        SevError = `TRUE;
                        $display("!Error: Access time INVALID: it must be 100 ns or 110 ns!!!");
                end


        if (SevError) $finish;
end

endmodule

// ******************************************
//
// Configuration Register module :
//
//      implements the configuration register
//
// ******************************************

module ConfigRegModule(address,Info); 


input [`ADDRBUS_range] address;
input Info;

reg [`ConfigurationReg_dim - 1 : 0] CR_reg;
reg [`BYTE_range] Status;

// **********************
//
// Setting default values
//
// **********************
 
`define ReadMode_bit         15
`define ClockLatency_MSBbit  14
`define ClockLatency_LSBbit  11
`define WaitPolarity_bit     10
`define WaitConfig_bit       8
`define BurstType_bit        7
`define ValidClockEdge_bit   6
`define WrapBurst_bit        3
`define BurstLength_MSBbit   2
`define BurstLength_LSBbit   0

// Interpreter  Config Reg\\

wire isASynchronous      = CR_reg[`ReadMode_bit] ? `TRUE : `FALSE;
wire isSynchronous       = CR_reg[`ReadMode_bit] ? `FALSE : `TRUE;
wire [3:0] Xlatency      = (CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit]<2 &&
                            CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit]>15) ? 0 : 
                            CR_reg[`ClockLatency_MSBbit : `ClockLatency_LSBbit];
wire isWaitPolActiveHigh = CR_reg[`WaitPolarity_bit] ? `TRUE : `FALSE;
wire isWaitBeforeActive    = CR_reg[`WaitConfig_bit] ? `TRUE : `FALSE;
wire isRisingClockEdge   = CR_reg[`ValidClockEdge_bit] ? `TRUE : `FALSE;
wire isWrapBurst         = CR_reg[`WrapBurst_bit] ? `FALSE : `TRUE;
wire isNoWrapBurst       = CR_reg[`WrapBurst_bit] ? `TRUE : `FALSE;

wire [4:0] BurstLength   = CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 1 ? 4 : 
                           CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 2 ? 8 : 
                           CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 3 ? 16:
                           0; // continous Burst


wire [2:0] BurstLength_bit = CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 1 ? 2 : 
                           CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 2 ? 3 : 
                           CR_reg[`BurstLength_MSBbit : `BurstLength_LSBbit] == 3 ? 4:
                           0; // continous Burst

initial begin    
        Status = `NoError_msg;
        CR_reg = `ConfigReg_default; 
end 

always @(isSynchronous) begin
if (Info)
   if (isSynchronous) 
        $write("[%t]  Synchronous Read Mode\n",$time);
   else 
        $write("[%t]  ASynchronous Read Mode\n",$time);
end 
// **********************
//
// ReSet to default value
//
// **********************
always @(Kernel.ResetEvent) begin 
        Status = `NoError_msg;
        CR_reg = `ConfigReg_default;
end

// **************************************
//
// FUNCTION getConfigReg :
//
//      return the Configuration Register
//
// **************************************

function [`ConfigurationReg_dim - 1 : 0] getConfigReg;
input required;
  begin
                getConfigReg = CR_reg;
  end
endfunction

// *************************************
//
// FUNCTION putConfigReg :
//
//      write the Configuration Register
//
// *************************************

task putConfigReg;
output [`BYTE_range] outStatus;
  
reg [`BYTE_range] outStatus;

integer count;

begin
        
        CR_reg = address[`ConfigurationReg_dim - 1 : 0];

        outStatus = Status;        

end
endtask


endmodule


// ***************************
//
// Electronic Signature Module
//
// ***************************

module SignatureModule; 



reg error;
integer i;
integer n_block;
  
initial begin
        end 

always @(posedge error)  begin
        Kernel.SetWarning(`RSIG_cmd,16'h00,`SignAddrRange_msg);
        error = `FALSE;
end
  
function [`WORD_range] Get;

input [`ADDRBUS_range] address;
  
    begin
      if (address[`SignAddress_range] == 9'h00) 
                begin
                        Get[`LOW_range] = `ManufacturerCode; 
                        Get[`HIGH_range] = 8'h00;
                end
      else if (address[`SignAddress_range] == 9'h01)
                begin 
                        if (`organization == "top") Get[`LOW_range] = `TopDeviceCode;  
                        else if (`organization == "bottom") Get[`LOW_range] = `BottomDeviceCode; 
                        else  Get[`LOW_range] = `SymDeviceCode;

                        Get[`HIGH_range] = 8'h89;
                end
      else if (address[`SignAddress_range] == 9'h02)
                begin 
                        Get[`LOW_range] = { 6'b0, BlockLock_man.getLockDownBit(address), BlockLock_man.getLockBit(address) };
                        Get[`HIGH_range] =  8'h00; 
                end
      else if (address[`SignAddress_range] == 9'h05)                       // Configuration Register
                        Get = ConfigReg_man.getConfigReg(0);       
      else if ((address[`SignAddress_range] >= `REGSTART_addr) && (address[`SignAddress_range] <= `REGEND_addr)) 
                begin 
                         Get = ProtectReg_man.RegisterMemory[address[`SignAddress_range] - `REGSTART_addr ];

                end
      else begin
                Get = 8'hXX;
                error = `TRUE;
           end
    end
endfunction
    
endmodule

// ********************
//
// CUI decoder module :
//      decode commands
//
// ********************

module CUIdecoder1(DataBus,Name,Cmd,CmdAllowed,Info);
input [`BYTE_range] DataBus, Cmd;
input [8*35:1] Name;
input Info;
input  CmdAllowed;         
always @Kernel.CUIcommandEvent begin
//#1;
  if (DataBus == Cmd  && CmdAllowed) begin  // is a First Command ?
//    #1 -> Kernel.VerifyEvent;
    -> Kernel.VerifyEvent;
    Kernel.CommandDecode1[Cmd] = !Kernel.CommandDecode1[Cmd];
    if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
  end
  else begin
    if (`FALSE) $display("[%t]  The %0s instruction decode unit is waiting for operation to complete.",$time,Name);
    @(Kernel.CompleteEvent or Kernel.ErrorEvent)
      if (`FALSE) $display("[%t]  The %0s instruction decode unit is listening for next command.",$time,Name);
  end
end
endmodule


// ********************
//
// CUIdecoder manager :
//      decode commands
//
// ********************

module CUIdecoder2(DataBus,Name,Cmd1,Cmd2,CmdAllowed,Info);
input [`BYTE_range] DataBus, Cmd1, Cmd2;
input [8*27:1] Name;
input Info;
input  CmdAllowed; 

always @Kernel.CUIcommandEvent begin
  if (DataBus == Cmd1 && CmdAllowed) begin
     #1 -> Kernel.VerifyEvent;

    @Kernel.CUIcommandEvent

      if (DataBus == Cmd2 && CmdAllowed)  begin
      #1  -> Kernel.VerifyEvent;

        Kernel.CommandDecode2[{Cmd1,Cmd2}] = !Kernel.CommandDecode2[{Cmd1,Cmd2}];
        if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
      end
  end
  else begin
    if (`FALSE) $display("%t  The %0s instruction decode unit is waiting for operation to complete.",$time,Name);
    @(Kernel.CompleteEvent or Kernel.ErrorEvent)
      if (`FALSE) $display("%t  The %0s instruction decode unit is listening for next command",$time,Name);
  end
end

endmodule



// ****************************
//
// CUI Decoder Manager :
//      decode the cUI commands
//
// ****************************

module CUIdecoder_Busy1(DataBus,Name,Cmd,CmdAllowed,Info);
input [`BYTE_range] DataBus, Cmd;
input [8*8:1] Name;
input Info;
input  CmdAllowed; 
          
always @Kernel.CUIcommandEvent begin
  if ((DataBus == Cmd) && CmdAllowed) begin
    -> Kernel.VerifyEvent;
    Kernel.CommandDecode1[Cmd] = !Kernel.CommandDecode1[Cmd];
    if (Info) $display("[%t]  Command Issued: %0s",$time,Name);
  end



end

endmodule

// Erase Manager
// manage the erase functionality

module EraseModule(address, data, progVoltOK, progHighVoltOK,Info);
  input [`ADDRBUS_range] address;
  input [`WORD_range] data;

  input progVoltOK, progHighVoltOK;

  input Info;
  event ErrorCheckEvent, CompleteEvent;

  reg [`BYTE_range] Status;
  reg [`ADDRBUS_range] hold_address;
  reg [`BLOCKADDR_range] hold_block;

  reg Busy, Suspended, first_time;
  integer i;
  time startTime, delayTime,Erase_time;

  initial begin                   // constructor sequence             
    Busy       = `FALSE;                                                    
    Suspended  = `FALSE;                                               
    Erase_time = `MainBlockErase_time;
    delayTime  =  Erase_time;                                      

  end         
     

  function IsBusy;                // boolean function primitive       
  input obbl;                     // all functions require a parameter
    IsBusy = Busy;                // return Boolean value             
  endfunction                                                         
                                                                      
  function IsSuspended;           // boolean function primitive       
  input obbl;                     // all functions require a parameter
    IsSuspended = Suspended;      // return Boolean value             
  endfunction                                                         
  
  function IsAddrSuspended;       // boolean function primitive       
  input [`ADDRBUS_range] addr;
    IsAddrSuspended = (Suspended && (addr == hold_address));
  endfunction
                                                                   
  function IsBlockSuspended;       // boolean function primitive       
  input [`ADDRBUS_range] addr;
    IsBlockSuspended = (Suspended && ((BankLib_man.getBlock(addr) == BankLib_man.getBlock(hold_address))));
  endfunction
 
  // *********************
  //
  // Task checkConfirm :
  //    check confirm code
  //
  // *********************

  task checkConfirm;
  
  output  [`BYTE_range] outStatus;
  
  reg     [`BYTE_range] outStatus;

  begin

        if (data == `BLKEEconfirm_cmd) outStatus = `NoError_msg;
        else outStatus = `WrongEraseConfirm_msg;

  end
  endtask


  task Suspend;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin
      delayTime = delayTime - ($time - startTime);
      #`EraseSuspendLatency_time
      outStatus = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
    end
  endtask

  task Resume;
  output [`BYTE_range] Status;
    begin
      Suspended = `FALSE;
      BlockErase(Status);
    end
  endtask
  
  task BlockErase;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin


                if (progHighVoltOK) 
                    if (BankLib_man.isMainBlock(address)) Erase_time = `FastMainBlockErase_time;
                                                    else  Erase_time = `FastParameterBlockErase_time;
                else 
                    if (BankLib_man.isMainBlock(address)) Erase_time = `MainBlockErase_time;
                                                    else  Erase_time = `ParameterBlockErase_time;

                delayTime    = Erase_time;
                hold_address = address;
                hold_block   = BankLib_man.getBlock(address);



      fork
        begin : Operation
          Busy = `TRUE;
          startTime = $time;
          -> ErrorCheckEvent;
          #delayTime Memory_man.EraseBlock(hold_block,Status);
          delayTime = Erase_time;
          -> CompleteEvent;
        end
        @CompleteEvent
          disable Operation;
      join
      outStatus = Status;
      Busy = `FALSE;
    end
  endtask

  always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
    if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
    else if (Memory_man.IsBlockSuspended(hold_address))      
      Status = `SuspCmd_msg;
    else if (!progVoltOK)
      Status = `InvVDD_msg;

    if (Status != `NoError_msg)
      ->CompleteEvent;
    else
      fork : ErrorCheck
        @(negedge progVoltOK) Status = `InvVDD_msg;
        @(Status) -> CompleteEvent;
        @(CompleteEvent) disable ErrorCheck;
      join
  end

endmodule  //end module Erase

// *********************
//
// Memory Manager :
//      the memory array
// 
// *********************


module MemoryModule(Info);

input Info;
reg [`WORD_range] memory [0:(`MEMORY_dim) - 1];     // the Memory: word organization

initial begin 
        LoadMemory;
end 

  task LoadMemory;                                // Initialize and load the memory from a file
  integer i;     
    begin
      #0 if (Info) $display("[%t] Inizialize the Memory to default value",$time);
     
      for (i = 0; i < `MEMORY_dim; i = i + 1)  memory[i] = {16{`HIGH}};    // Memory Init
      
      if (`FILENAME_mem !== "") begin 
           $readmemb(`FILENAME_mem, memory);

          if (Info) $display("[%t] Load Memory from file: %s",$time, `FILENAME_mem);
          else if (Info) $display("[%t] Warning: File: %s not found",$time, `FILENAME_mem);
      end
    end
  endtask


  function [`WORD_range] Get;
  input [`ADDRBUS_range] address;
        Get = memory[address];
  endfunction

   
  function IsSuspended;
  input [`ADDRBUS_range] address;
    IsSuspended = Program_man.IsAddrSuspended(address) || Erase_man.IsAddrSuspended(address) || ProgramBuffer_man.IsAddrSuspended(address);
  endfunction

  function IsBlockSuspended;
  input [`ADDRBUS_range] address;
    IsBlockSuspended = Program_man.IsBlockSuspended(address) || Erase_man.IsBlockSuspended(address);
  endfunction


  task Program;
  input [`WORD_range] data;
  input [`ADDRBUS_range] address;
  output [`BYTE_range] Status;
    begin
      Status = `NoError_msg;
      memory[address] = memory[address] & data;
      if (memory[address] != data) Status = `PreProg_msg;
    end
  endtask
  
  task EraseBlock;
  
  input [`INTEGER] block;
  
  output [`BYTE_range] ErrFlag;

  reg [`ADDRBUS_range] start_address;
  reg [`ADDRBUS_range] end_address;
  reg [`ADDRBUS_range] address;

  
  begin
      ErrFlag = `NoError_msg;
      start_address = BankLib_man.getBlockAddress(block);
      end_address   = BankLib_man.BlockBoundaryEndAddr[block];

      if (start_address > end_address)
        begin
                address = start_address;
                start_address = end_address;
                end_address = address;
        end
        
      
      for (address = start_address; address <= end_address; address = address + 1)
        memory[address] = `WORDNP;

  end
  endtask

   task BlockBlankCheck;
  
  input [`INTEGER] block;
  
  output [`BYTE_range] ErrFlag;

  reg [`ADDRBUS_range] start_address;
  reg [`ADDRBUS_range] end_address;
  reg [`ADDRBUS_range] address;

  
  begin
      ErrFlag = `NoError_msg;
      start_address = BankLib_man.BlockBoundaryStartAddr[block];
      end_address   = BankLib_man.BlockBoundaryEndAddr[block];

      if (start_address > end_address)
        begin
                address = start_address;
                start_address = end_address;
                end_address = address;
        end
        
      ErrFlag = `NoError_msg;
      address = start_address;
      while (memory[address] == `WORDNP && address <= end_address ) 
        address = address + 1;
      if (memory[address] != `WORDNP)  
        ErrFlag = `BlankCheckFailed_msg;

  end
  endtask 
  

  
endmodule //end MemoryModule 


// ***************************************
//
// Output Buffer :
//
//      manage the communication between 
//      the memory and the output data bus
//
// ***************************************

module OutputBufferModule(DataInput, DataInputBurst, DataOutput, OutputEnable);
  input [`WORD_range] DataInput;
  input [`WORD_range] DataInputBurst;
  output [`WORD_range] DataOutput;
  input OutputEnable;
  reg [`WORD_range] DataOutput;
  time timeDataV, timeDataX, timeDataZ;
  
  initial begin
    timeDataV=0;
    timeDataX=0;
    timeDataZ=0;
    SetZ(0);
  end
  
 task SetValid;
  input [63:0] delayTime;
    begin

      if ((delayTime+$time > timeDataV) || (timeDataV < $time)) begin
        timeDataV = delayTime + $time;


        disable waitValid;


           disable goValid;

      end
    end
  endtask
  
  always   
 fork 
    begin:  goValid

      #(timeDataV - $time) 
        if (OutputEnable == 1'b0) begin 
        
             if (ConfigReg_man.isASynchronous) DataOutput = DataInput;
                                          else DataOutput = DataInputBurst;
        end                                  
        
    end // goValid
    begin: waitValid
      wait (`FALSE);
    end
  join
  
 task SetX;
  input [63:0] delayTime;
    begin
      if ((delayTime+$time < timeDataX) || (timeDataX < $time)) begin
        timeDataX = delayTime + $time;
        disable waitX;


      end
    end
  endtask
  
  always fork
    begin : goX
      #(timeDataX - $time) if ((OutputEnable == `LOW) || (timeDataZ > timeDataX))
        DataOutput = 16'hX;
    end // goX
    begin: waitX
      wait (`FALSE);
    end
  join
  
  task SetZ;
  input [63:0] delayTime;
    begin
      if ((delayTime+$time < timeDataZ) || (timeDataZ < $time)) begin
        timeDataZ = delayTime + $time;
        disable waitZ;
        if (timeDataZ < timeDataV)
          disable goValid;
        if (timeDataZ < timeDataX)
          disable goX;
      end
    end
  endtask
  
  always begin: waitZ
    #(timeDataZ - $time) DataOutput = 16'hZ;
    wait (`FALSE);
  end
  
endmodule




// *********************************
//
// Program module :
//
//      manage the program operation
//
// *********************************

module ProgramModule(address,data, progVoltOK, progHighVoltOK, Info);
  input [`WORD_range] data;
  input [`ADDRBUS_range] address;
  input progVoltOK,progHighVoltOK;
  input Info;
  event ErrorCheckEvent, CompleteEvent;
  reg [`BYTE_range] Status;
  reg [`WORD_range] hold_data;
  reg [`ADDRBUS_range] hold_address;
  reg Busy, Suspended;

  integer i;
  time startTime, delayTime, WordProgram_time;

  initial begin                 // constructor sequence
    Busy = `FALSE;
    Suspended = `FALSE;
    WordProgram_time = `WordProgram_time;
    delayTime = WordProgram_time;
  end

  always @(progHighVoltOK) begin
     if (progHighVoltOK) WordProgram_time=`FastWordProgram_time;
                    else WordProgram_time=`WordProgram_time;
  end 
 
  function IsBusy;              // boolean function primitive
  input obbl;                   // all functions require a parameter
    IsBusy = Busy;              // return Boolean value
  endfunction
  
  function IsSuspended;         // boolean function primitive
  input obbl;                   // all functions require a parameter
    IsSuspended = Suspended;    // return Boolean value 
  endfunction                                                       

  function IsAddrSuspended;     // boolean function primitive       
  input [`ADDRBUS_range] addr;
    IsAddrSuspended = (Suspended && (addr == hold_address));
  endfunction

  function IsBlockSuspended;    // return true if block is suspended
  input [`ADDRBUS_range] addr;
    IsBlockSuspended = (Suspended && (BankLib_man.getBlock(addr) == BankLib_man.getBlock(hold_address)));
  endfunction
  

  task Suspend;
  output [`BYTE_range] suspErrFlag;
  reg [`BYTE_range] suspErrFlag;
    begin
      delayTime = delayTime - ($time - startTime);
      #`ProgramSuspendLatency_time suspErrFlag = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
    end
  endtask

  task Resume;
  output [`BYTE_range] ErrFlag;
    begin
      Suspended = `FALSE;
      Program(ErrFlag);
    end
  endtask

  task Program;
  output [`BYTE_range] outErrFlag;
  reg [`BYTE_range] outErrFlag;
    begin
      if (delayTime == WordProgram_time) begin
        hold_data = data;
        hold_address = address;
      end
      fork
        begin : Operation
          Busy = `TRUE;
          startTime = $time;
          -> ErrorCheckEvent;
          #delayTime Memory_man.Program(hold_data,hold_address,Status);
          delayTime = `WordProgram_time;
          -> CompleteEvent;
        end
        @CompleteEvent disable Operation;
      join
      outErrFlag = Status;
      Busy = `FALSE;
    end
  endtask

  always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
    if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
    else
    if (Memory_man.IsSuspended(hold_address))
      Status = `SuspAcc_msg;
    else if (!progVoltOK)
      Status = `InvVDD_msg;

    if (Status != `NoError_msg) ->CompleteEvent;
    else
      fork : ErrorCheck
        @(negedge progVoltOK) Status = `InvVDD_msg;
        @(Status) -> CompleteEvent;
        @(CompleteEvent) disable ErrorCheck;
      join
  end
  
endmodule // end PrograModule 



// *********************************
//
// Buffer Ehnanced Program module :
//
//      program buffer functionality
//
// *********************************

module BuffEnhancedFactProgramModule(address, data, progVoltOK, progHighVoltOK, Info);


  input [`ADDRBUS_range] address;
  input [`WORD_range] data;
  input progVoltOK, progHighVoltOK, Info;

  event ErrorCheckEvent,ErrorCheckEvent_inVerify, CompleteEvent, WatchAddressEvent;
  reg [`BYTE_range] Status;
  reg [`WORD_range] hold_data, hold_StartBlock;
  reg [`ADDRBUS_range] hold_address, startAddress;
  reg [`WORD_range] bufferData [`BuffEnhProgramBuffer_range];

  reg Busy;
  time Program_time;
  integer i,Len;
  
  initial begin                 // constructor sequence             
    Busy = `FALSE;
    Status = `NoError_msg;
    Program_time = `WordProgram_time;
    EmptyBuffer;
  end                                                               

  task EmptyBuffer;
    begin 
        for (i = 0; i < `BuffEnhProgramBuffer_dim; i = i + 1) 
             bufferData[i] = 16'hFFFF;
        Len=0;
    end 
  endtask
  
  function IsBusy;              // boolean function primitive       
  input obbl;               // all functions require a parameter
    IsBusy = Busy;              // return Boolean value             
  endfunction                                                       
 
  task Setup;
  output [`BYTE_range] outStatus;
    begin
      Status = `NoError_msg;
      Len=0;
      startAddress = address;
      hold_address = address;
      hold_StartBlock   = BankLib_man.getBlock(address);
      -> ErrorCheckEvent; 
        #0 outStatus=Status;
      if (Status == `NoError_msg) begin 
        if (Info) $display("[%t]  Enhanced Factory Program -> Setup Phase",$time);
        if (Info) $display("[%t]  Enhanced Factory Program: Start address: %h",$time,startAddress);
        #`EnhBuffProgramSetupPhase_time;
        Busy = `TRUE;
      end
    end 
  endtask
  
  task Exit;
  output [`BYTE_range] outStatus;
    begin
      Busy = `FALSE;
      outStatus = Status;
      if (Info) $display("[%t]  Enhanced Factory Program -> Exit Phase",$time);
      if (Len != `BuffEnhProgramBuffer_dim)  
        $display("[%t] Warning --- The buffer must be completely filled for programming to occur",$time);
    end
  endtask
   
  task Load;
  output [`BYTE_range] outStatus;
    begin
      if (BankLib_man.getBlock(address) != hold_StartBlock) Status = `ExitPHASE_BEFP_msg;
      else begin
          bufferData[Len] = data;
          if (Info) $display("[%t]  Enhanced Factory Program -> Load: data[%d]=%h ",$time,Len,bufferData[Len]);
          Len = Len + 1;
          if (Len == `BuffEnhProgramBuffer_dim) Status = `ProgramPHASE_BEFP_msg;
          
          end 
      outStatus = Status;
    end
  endtask
  
  task Program;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin
      fork
        begin : Operation
          if (Info) $display("[%t]  Enhanced Factory Program {Program Phase}",$time); 
          #`EnhBuffProgram_time

          if (Info) $display("[%t]  Enhanced Factory Program {End of Program Phase}",$time); 
           for (i = startAddress;i < (`BuffEnhProgramBuffer_dim + startAddress); i = i + 1) begin
               Memory_man.Program(bufferData[i - startAddress],i,Status);
           end
         -> CompleteEvent;      //end of program
         end
      @CompleteEvent begin 
          disable Operation;
       end 
      join
          if (Status == `ProgramPHASE_BEFP_msg) begin //prova
             Status = `NoError_msg;
          end
          outStatus = Status;
    end
  endtask

 always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
    if (BlockLock_man.IsLocked(hold_address))
      Status = `BlockLock_msg;
    else if (!progVoltOK)
      Status = `InvVDD_msg;
    else if (!progHighVoltOK)
      Status =  `InvVPP_msg;
    if (Status != `NoError_msg)
      ->CompleteEvent;
    else
      fork : ErrorCheck
        @(negedge progVoltOK) Status = `InvVDD_msg;
        @(negedge progHighVoltOK) Status = `InvVPP_msg;
        @(CompleteEvent) disable ErrorCheck;
      join
  end
  


endmodule



// ******************************************
//
// Protect Register module :
//
//      operations on the protection register
//
// ******************************************

module ProtectRegModule(address, data, voltOK, Info);
  input [`ADDRBUS_range] address;
  input [`DATABUS_range] data;
  input voltOK, Info;
  reg [`WORD_range] RegisterMemory[`REG_dim - 1 :0];
  reg [`BYTE_range] Status;
  reg Busy;
  reg [`ADDRBUS_range] AddressLatched;
  event ErrorCheckEvent, CompleteEvent;
  integer i;
  reg [`ADDRBUS_range] hold_addr;
  reg [`DATABUS_range] hold_data;


  initial begin                         // constructor sequence             
    Busy = `FALSE;
    RegisterMemory[0] = `PRL_default;
    for (i = 1; i < `REG_dim; i = i + 1) begin
      RegisterMemory[i] = `WORDNP;
    end  
  end
  
  
  function IsBusy;              // boolean function primitive       
  input required;               // all functions require a parameter
    IsBusy = Busy;              // return Boolean value             
  endfunction                                                       
                                                                    
  function UDNisLocked;            // boolean function primitive
  input obbl;                           // input is required
  if ((RegisterMemory[`PROTECTREGLOCK_addr - `REGSTART_addr] | `UDNprotect_bit) == `UDNprotect_bit) 
              UDNisLocked = `TRUE;
      else 
              UDNisLocked = `FALSE;
  endfunction
        
  function UPisLocked;            // boolean function primitive
  input obbl;                   // input is required
        UPisLocked = ((RegisterMemory[`PROTECTREGLOCK_addr - `REGSTART_addr] | `UPprotect_bit) == `UPprotect_bit) ? `TRUE : `FALSE;
  endfunction

  function isUDNaddress;
  input [`ADDRBUS_range] address;
    if ((address >= `UDNREGSTART_addr) && ( address <= `UDNREGEND_addr)) // Check UDN register Address Bound 
         isUDNaddress = `TRUE;
    else isUDNaddress = `FALSE;
  endfunction

 function isUPaddress;
  input [`ADDRBUS_range] address;
    if ((address >= `UPREGSTART_addr) && (address <= `UPREGEND_addr)) // Check UP register Address Bound 
         isUPaddress = `TRUE;
    else isUPaddress = `FALSE;
  endfunction

function [`BYTE_range] ExtIndexPRL;            // bit index of PRL register 
  input [`ADDRBUS_range]addr;
 ExtIndexPRL=(addr - `ExtREGSTART_regionaddr) / `ExtREG_regiondim;
  endfunction
  
 function isExtLocked;            // boolean function primitive
  input [`ADDRBUS_range] addr;                   // input is required
  reg [`BYTE_range] bitIndex;
    begin 
      bitIndex = ExtIndexPRL(addr);  // protect bit index of Extended Protection Register Memory
      isExtLocked = !(RegisterMemory[(`ExtPROTECTREGLOCK_addr - `REGSTART_addr)][bitIndex]);
    end
  endfunction

 function isExtValidAddress;
  input [`ADDRBUS_range] address;
    if ((address >= `ExtREGSTART_regionaddr) && (address <= `ExtREGEND_regionaddr) ) // Check ExtRegister Address Bound 
         isExtValidAddress = `TRUE;
    else isExtValidAddress = `FALSE;
  endfunction

  task Program;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin
      Busy = `TRUE;
      hold_addr = address[`REG_addrbitRange];
      hold_data = data;
      if (Info) $write("[%t]  OTP Program Memory[%h]=%h\n",$time,hold_addr,data);
      fork
        begin : Operation
          -> ErrorCheckEvent;
          #`WordProgram_time RegisterMemory[hold_addr - `REGSTART_addr] = RegisterMemory[hold_addr - `REGSTART_addr] & hold_data;   
          -> CompleteEvent;
        end
        @CompleteEvent disable Operation;
      join
      outStatus = Status;
      Busy = `FALSE;
    end
  endtask
  
  always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
    if (( address < `REGSTART_addr) || ( address > `REGEND_addr)) // Check Address Bound 
         Status = `AddrRange_msg;
    else if ( isUDNaddress(address) && UDNisLocked(1'bX) ) 
         Status = `UDNlock_msg;
    else if ((isUPaddress(address) && UPisLocked(1'bX)))
         Status = `UPlock_msg;
    else if ( isExtValidAddress(hold_addr) & isExtLocked(hold_addr) )
         Status = `ExtREGLock_msg;
 
    else if (Kernel.Suspended)
      Status = `SuspCmd_msg;
    else if (!voltOK)
      Status = `InvVDD_msg;

    if (Status != `NoError_msg)
      ->CompleteEvent;
    else
      fork : ErrorCheck
        @(negedge voltOK) Status = `InvVDD_msg;
        @(Status) -> CompleteEvent;
        @(CompleteEvent) disable ErrorCheck;
      join
  end
endmodule //end ProtectRegModule



// Read Manager
// Manage the read operation

module ReadModule(dataOutput,address,voltOK,Info);

  output [`WORD_range] dataOutput;
  input [`ADDRBUS_range] address;
  input voltOK;
  input Info;
  reg [`WORD_range] dataOutput, regRead;
  reg [1:0] Mode, oldMode;
  reg [`BYTE_range] Status;
  
  integer i;
  
  initial begin
    regRead = 0; 
    Mode = `ReadArray_bus;
    oldMode = `ReadArray_bus;
    dataOutput = `DATABUS_dim'hzzzz;
  end

  task SetMode;
  input [1:0] newMode;
  output [`BYTE_range] Status;
    begin
      Status = `NoError_msg;
      if (Info && (newMode!=Mode)) begin
        case (newMode)
          `ReadArray_bus        : $display ("[%t]  Device now in Read Array mode ", $time);
          `ReadCFI_bus          : $display ("[%t]  Device now in Read CFI mode ", $time);
          `ReadSignature_bus    : $display ("[%t]  Device now in Read Electronic Signature Mode ", $time);
          `ReadStatusReg_bus    : $display ("[%t]  Device now in Read Status Register Mode ", $time); 
          default               : $display ("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
        endcase
       end
        oldMode=Mode;
        Mode = newMode;

    end
  endtask

 
  always @Kernel.ResetEvent begin
        Mode = `ReadArray_bus;
  end
 
  always @(negedge Kernel.Ready) begin   // Configure according to status register
    Mode = `ReadStatusReg_bus;
  end
 
  always @Kernel.ReadEvent begin          // Main execution of a read is based on an event


    case (Mode)
      `ReadArray_bus       : begin 
                              dataOutput = Memory_man.Get(address);
                              if (Info) $display("[%t]  Data Read result: memory[%h]=%h", $time,address,dataOutput);
                             end 
      `ReadCFI_bus         : begin
                              dataOutput = CFIquery_man.Get(address);
                              if (Info) $display("[%t]  Data Read result: CFI_memory[%h]=%h", $time,address,dataOutput);
                             end  
      `ReadSignature_bus   :  begin
                             dataOutput = Signature_man.Get(address);
                             if (Info) $display("[%t]  Read Device Identifier(addr=%h) :%h", $time,address,dataOutput);
                              end

      `ReadStatusReg_bus   : begin
                              dataOutput = SR_man.SR;
                               if (Info) $display("[%t]  Read Status Register: %b", $time,dataOutput[`BYTE_range]);
                             end 

      default              : $display("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
    endcase
    if ((Mode == `ReadArray_bus) && (Memory_man.IsSuspended(address) == `TRUE)) begin
      dataOutput = 16'hXX;
      Kernel.SetWarning(`RD_cmd,8'hXX,`SuspAcc_msg);
    end
  end

endmodule
// end Module Read


// *************************************************
//
// Status Register module :
//
//      implements the Status Register of the device
//
// *************************************************

module StatusRegModule(Info);
  input Info;
  

 reg EraseStatus, ProgramStatus, 
     VpenStatus, BlockProtectionStatus, BW_status;

  reg [`BYTE_range] Status;
  
  wire [7:0] SR = {Kernel.Ready,                        // bit 7 
                   Erase_man.IsSuspended(1'bX),         // bit 6
                   EraseStatus,                         // bit 5
                   ProgramStatus,                       // bit 4
                   VpenStatus,                          // bit 3
                   Program_man.IsSuspended(1'bX) ||  ProgramBuffer_man.IsSuspended(1'bX),       // bit 2
                   BlockProtectionStatus,               // bit 1
                   BW_status};                          // bit 0
  wire [7:0] SR_Info =  SR;


  
  //-----------------
  // Init
  //-----------------
  
  initial begin
          EraseStatus=1'b0;
          ProgramStatus=1'b0;
          VpenStatus=1'b0;
          BlockProtectionStatus=1'b0;
          BW_status=1'b0;
  end


  always @(SR_Info) if (Kernel.Ready!=1'bZ)
    if (Info) $display("[%t]  Status Register Update: %b",$time, SR_Info);
  
  always @(Kernel.ResetEvent) begin
    Clear(Status);
  end


   always @(Kernel.Ready,ProtectReg_man.Busy, BuffEnhancedFactProgram_man.Busy) 
  begin
  if (Kernel.Ready) 
                BW_status = `FALSE;
           else 
             if (BuffEnhancedFactProgram_man.Busy == `TRUE) 
                   BW_status=`TRUE;

  end
  
  always @(Kernel.ErrorEvent) begin //Update status register bits upon specific errors
    #0;
    case(Kernel.GetError(1'bX))
      `InvVDD_msg       : begin VpenStatus = `TRUE; end
      `InvVPP_msg       : begin VpenStatus = `TRUE; end
      `BlockLock_msg    : begin BlockProtectionStatus = `TRUE;  end
      `UDNlock_msg      : begin ProgramStatus = `TRUE; end
      `UPlock_msg       : begin ProgramStatus = `TRUE; end

      `ProtRegAddrRange_msg : begin 
                                BlockProtectionStatus = `TRUE;  
                          end
      `ExtREGLock_msg   : begin 
                                BlockProtectionStatus = `TRUE;  
                          end

      `CmdSeq_msg       : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `AddrRange_msg    : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `AddrTog_msg      : begin ProgramStatus = `TRUE; EraseStatus = `TRUE; end
      `PreProg_msg      : begin ProgramStatus = `TRUE; end 
      `WrongEraseConfirm_msg : begin ProgramStatus = `TRUE; EraseStatus   = `TRUE; end
      `WrongBlankCheckConfirm_msg : begin 
                                ProgramStatus = `TRUE; EraseStatus   = `TRUE; 
                                end
       `BlankCheckFailed_msg : begin 
                                EraseStatus   = `TRUE; 
                                end
        `LeastAddr0:        begin 
                                ProgramStatus = `TRUE; 
                              end
                        

    endcase
    case(Kernel.GetCmd(4'h1))
      `PG_cmd           : begin ProgramStatus = `TRUE; end
      `PRREG_cmd        : begin ProgramStatus = `TRUE; end
      `PB_cmd           : begin ProgramStatus = `TRUE; end
      `BLKEE_cmd        : begin EraseStatus = `TRUE; end
      `BL_cmd           : if (Kernel.GetCmd(4'h2) == `BLconfirm_cmd) ProgramStatus = `TRUE;
      `BUL_cmd          : if (Kernel.GetCmd(4'h2) ==`BULconfirm_cmd) EraseStatus   = `TRUE;
      `BLD_cmd          : if (Kernel.GetCmd(4'h2) ==`BLDconfirm_cmd) ProgramStatus = `TRUE;
      `BuffEnhProgram_cmd :
                           if (Kernel.GetCmd(4'h2) == `BuffEnhProgramCfrm_cmd) 
                                        ProgramStatus = `TRUE;

    endcase 
  end  

  task Clear;
  output [`BYTE_range] Status;
    begin
      Status = `NoError_msg;
      EraseStatus = `FALSE;
      ProgramStatus = `FALSE;
      VpenStatus  = `FALSE;
      BlockProtectionStatus   = `FALSE;
      BW_status = `FALSE;
    end
  endtask

endmodule  // end module status register


// *************
//
// Kernel Module 
//
// *************

module KernelModule(VDD, VDDQ, VPP, Info);
  input [`Voltage_range] VDD, VDDQ, VPP;
  input Info;
  event CUIcommandEvent, VerifyEvent, ErrorEvent, CompleteEvent, ResetEvent, ReadEvent, ProgramCompleteEvent, EraseCompleteEvent;

  reg voltOK, progVoltOK, eraseVoltOK, lockVoltOK, ioVoltOK, lockOverrideOK;
  reg progHighVoltOK, eraseHighVoltOK;
  reg [8'hFF:0] CommandDecode1;
  reg [16'hFFFF:0] CommandDecode2;
  reg [7:0] lastStatus, lastCmd1, lastCmd2;
  
  // Device Status

  wire Ready = (!Program_man.Busy && !ProgramBuffer_man.Busy  && !BuffEnhancedFactProgram_man.Busy
                           && !Erase_man.Busy && !ProtectReg_man.Busy && !BlankCheck_man.Busy);
                          
                           
  wire Suspended = Program_man.Suspended || Erase_man.Suspended || ProgramBuffer_man.Suspended;
  
  initial begin                 // constructor sequence
    CommandDecode1 = 8'h00;         // initialize decode success status variables
    CommandDecode2 = 16'h0000;
  end
  
  
  always @(voltOK) begin
    if (!voltOK) begin 
                 $display("[%t]  !ERROR: Invalid VDD Voltage.",$time);
                 -> ErrorEvent;
                 end
             else     
                 $display("[%t]  VDD Voltage is OK",$time);
  end
  
  always @(ioVoltOK) begin
    if (!ioVoltOK) begin 
                   $display("[%t]  !ERROR: Invalid VDDQ I/O voltage.", $time);
                   -> ErrorEvent;
                   end 
               else 
                   $display("[%t]  VDDQ Voltage is OK",$time);
               
  end
  
  always @(VDD) begin        
    if ((VDD < `VDDmin) | (VDD > `VDDmax))
      voltOK = `FALSE;
    else
      voltOK = `TRUE;
  end


  always @(VDDQ) begin  // check i/o voltage constraints
    if ((VDDQ >= `VDDQmin) && (VDDQ <= `VDDQmax))
      ioVoltOK = `TRUE;
    else 
      ioVoltOK = `FALSE;
  end
 
  always @(VPP) begin // program/erase/lock
    if ((VPP>=`VPPmin && VPP<=`VPPmax))  begin
      progVoltOK  = `TRUE;
      eraseVoltOK = `TRUE;
      lockVoltOK  = `TRUE;
      progHighVoltOK  = `FALSE;
      eraseHighVoltOK = `FALSE;
    end
    else if ((VPP>=`VPPHmin) && (VPP<=`VPPHmax)) begin 
      progVoltOK  = `TRUE;
      eraseVoltOK = `TRUE;
      lockVoltOK  = `TRUE;
      progHighVoltOK  = `TRUE;
      eraseHighVoltOK = `TRUE;
    end
    else begin 
      progVoltOK  = `FALSE;
      eraseVoltOK = `FALSE;
      lockVoltOK  = `FALSE;
      progHighVoltOK  = `FALSE;
      eraseHighVoltOK = `FALSE;
    end 
  end
  

  function [7:0] GetError;
  input required;
    GetError = lastStatus;
  endfunction

  function [7:0] GetCmd;
  input commandNum;
    GetCmd = (commandNum == 1) ? lastCmd1 : lastCmd2;
  endfunction

  task SetWarning;
  input [7:0] Cmd1, Cmd2;
  input [7:0] Status;
    begin
      Report(Cmd1,Cmd2,Status);
      lastStatus = Status;
    end
  endtask

  task SetError;
  input [7:0] Cmd1, Cmd2;
  input [7:0] ErrFlag;
    begin
      SetWarning(Cmd1,Cmd2,ErrFlag);
      -> ErrorEvent; // Only errors set error event
    end
  endtask


 task Report;
  input [7:0] Cmd1, Cmd2;
  input [7:0] Status;
    begin
      lastStatus = Status;
      lastCmd1 = Cmd1;
      lastCmd2 = Cmd2;
        if ((lastStatus != `NoError_msg) || Info) begin //Display error .
          $write("[%t] ",$time);
           case(Status)
            `NoError_msg         : begin $write(" Command Completion Successful "); end
            `CmdSeq_msg         : begin $write(" !Error:   [Invalid Command]\n Sequence Command Unknown"); -> ErrorEvent; end
            `SuspCmd_msg        : begin $write(" !Error:   [Invalid Command]\n Cannot execute this command during suspend"); -> ErrorEvent; end
            `SuspAcc_msg        : begin $write(" !Error:   [Invalid Command]\n Cannot access this address due to suspend"); -> ErrorEvent; end
            `SignAddrRange_msg  : begin $write(" !Error:   [Invalid Address]\n Signature Address out of range"); end
            `CFIAddrRange_msg   : begin $write(" !Error:   [Invalid Address]\n CFI Address out of range"); end
            `AddrRange_msg      : begin $write(" !Error:   [Invalid Address]\n Address out of range"); -> ErrorEvent; end
            `AddrTog_msg        : begin $write(" !Error:   [Program Buffer]\n Cannot change block address during command sequence"); -> ErrorEvent; end
            `BuffSize_msg       : begin $write(" !Error:   [Program Buffer]\n Buffer size is too large (Max Size is %d) ",`ProgramBuffer_dim); -> ErrorEvent; end
            `InvVDD_msg         : begin $write(" !Error:   [Invalid Supply]\n Voltage Supply must be: VDD>VDDmin and VDD<VDDmax "); -> ErrorEvent; end
            `InvVPP_msg         : begin $write(" !Error:   [Invalid Program Supply]\n Program Supply Voltage must be: VPP>VPPHmin and VPP<VPPHmax for this Operation"); -> ErrorEvent; end
            `ByteToggle_msg     : begin $write(" !Error:   [BYTE_N Toggled]\n Cannot toggle BYTE_N while busy"); -> ErrorEvent; end
            `PreProg_msg        : begin $write(" !Error:   [Program Failure]\n Program Failure due to cell failure"); -> ErrorEvent; end 
            `UDNlock_msg        : begin $write(" !Error:   [Program Failure]\n Unique Device Number Register is locked"); -> ErrorEvent; end
            `UPlock_msg         : begin $write(" !Error:   [Program Failure]\n User Programmable Register is locked"); -> ErrorEvent; end
            `ExtREGLock_msg     : begin $write(" !Error:   [Program Failure]\n Extended User Programmable OTP is locked"); -> ErrorEvent; end
            `NoUnLock_msg       : begin $write(" #Warning: [Locked Down Warning]\n  Invalid UnLock Block command in Locked-Down Block"); end
            `SuspAccWarn_msg    : begin $write(" #Warning: [Invalid Access]\n  It isn't possible access this address due to suspend"); end
            `BlockLock_msg      : begin $write(" !Error:   [Locked Error]\n Cannot complete operation when the block is locked "); -> ErrorEvent; end
            `BlkBuffer_msg      : begin $write(" !Error: [Program Buffer]  Program Buffer cannot cross block boundary"); end
            `AddrCFI_msg        : begin $write(" #Warning: [Invalid CFI Address]\n CFI Address out of range"); end
            `NoBusy_msg         : begin $write(" #Warning: [NO Busy]\n Device is not Busy"); end
            `NoSusp_msg         : begin $write(" #Warning: [NO Suspend]\n Nothing previus suspend command"); end
            `Suspend_msg        : begin $write("  Suspend of "); end
            `WrongEraseConfirm_msg : begin
                                        $write(" !Error: [Wrong Erase Confirm Code "); 
                                        -> ErrorEvent;
                                     end   
            `LeastAddr0         : begin 
                                        $write(" !Error:   [Program Failure]\n Least Significative bit [%2d downto 0] of Start Address must be 0",`ProgramBuffer_addrDim-1); 
                                        -> ErrorEvent;
                                  end
                         
            `WrongBlankCheckConfirm_msg : begin
                                        $write(" !Error: [Confirm Code] Wrong Blank Check Confirm Code "); 
                                        -> ErrorEvent;
                                     end   

            `WrongBlankCheckBlock:  begin 
                                        $write(" !Error:   [Blank Check Failure]\n The block must be a main block"); 
                                        -> ErrorEvent;
                                    end

            `BlankCheckFailed_msg : begin $write(" !Error:   [Blank Check]\n Blank Check Failed "); 
                                        -> ErrorEvent; 
                                     end

            default             : begin $write(" !ERROR: [Unknown error]\n Flag=%h, cmd1=%hh, cmd2=%hh",Status,Cmd1,Cmd2); -> ErrorEvent; end
          endcase 
          case (Cmd1)
            16'hXX              : $display(" !Error: [General Error}\n Error not defined");
            `RD_cmd             : $display(" { Read Array }");
            `RSR_cmd            : $display(" { Read Status Register }");
            `RSIG_cmd           : $display(" { Read Electronic Signature }");
            `RCFI_cmd           : $display(" { Read CFI }");
            `PG_cmd             : $display(" { Program }");
            `BuffEnhProgram_cmd : $display(" { Buffer Enhanced Factory Program }");

            `SCR_cmd | `BL_cmd | `BUL_cmd |  `BLD_cmd 
                              : begin 
                                if (Cmd2 == `SCRconfirm_cmd) $display(" { Set Configuration Register }");
                                if (Cmd2 == `BLconfirm_cmd)  $display(" { Block Lock }");
                                if (Cmd2 == `BULconfirm_cmd) $display(" { Block UnLock }");
                                if (Cmd2 == `BLDconfirm_cmd) $display(" { Block Lock-Down }");
                                end
            `PER_cmd          : $display(" { Program/Erase Resume }");                    
            `PRREG_cmd        : $display(" { Protection Register Command }");
            `BLKEE_cmd        : $display(" { Block Erase }");
            `BLNKCHK_cmd      : $display(" { Blank Check }");
            `CLRSR_cmd        : $display(" { Clear Status Register }");
            `PES_cmd          : $display(" { Program/Erase Suspend }");
            `PB_cmd           : $display(" { Write to Buffer and Program }");
            default           : $display(" {unknown command:  %hh}", Cmd1);
          endcase
        end
    end
  endtask

  task CheckTime;
  input [8*6:1] tstr;
  input [31:0] tdiff, tprev;
  
  begin
    if ($time - tprev < tdiff) begin
      $display ("[%t]  !ERROR: %0s timing constraint violation:  %0d-%0d < %0dns ", $time, tstr, $time, tprev, tdiff);
      -> ErrorEvent;
    end
  end
  endtask

endmodule // end module Kernel 





module x28fxxxp30(A, DQ, W_N, G_N, E_N, L_N, K, WAIT, WP_N, RP_N, VDD, VDDQ, VPP, Info);

  // Signal Bus
  input [`ADDRBUS_dim-1:0] A;           // Address Bus 
  inout [`DATABUS_dim-1:0] DQ;          // Data I/0 Bus
  // Control Signal
  input  W_N;                           // Write Enable 
  input  G_N;                           // Output Enable
  input  E_N;                           // Chip Enable
  input  L_N;                           // Latch Enable
  input  K;                             // Clock
  input  WP_N;                          // Write Protect
  input  RP_N;                          // Reset/Power-Down

  // Voltage signal rappresentad by integer Vector which correspond to millivolts
  input [`Voltage_range] VDD;           // Supply Voltage
  input [`Voltage_range] VDDQ;          // Input/Output Supply Voltage
  input [`Voltage_range] VPP;           // Optional Supply Voltage for fast Program & Erase
  
  // Others Signal       
  output WAIT;                          // Wait
  reg wait_;
  assign WAIT = wait_;
  input Info;                           // Enable/Disable Information of the operation in the memory 
  wire  CLK;
  assign CLK = (K ~^ ConfigReg_man.isRisingClockEdge);
  reg CLOCK;
  // === Internal Signal ===
  // Chip Enable 
  wire  CE_N = E_N & Kernel.voltOK & RP_N;

  // Output Enable 
  wire  OE_N = G_N | CE_N | !Kernel.ioVoltOK | !RP_N;
  // Write Enable 
  wire  WE_N = W_N | CE_N;

  // Latch Enable

  wire  LE_N = L_N | CE_N;
  // === Bus Latch ===
  // Data Bus
  wire [`DATABUS_dim-1:0] DataBusIn;
  wire [`DATABUS_dim-1:0] DataBurst;
  
  // read burst is in wait state
  wire isWait; 

  // Address Bus
  reg [`ADDRBUS_dim - 1:0] AddrBusIn;
   
  // Status
  //aggiunti stati buffenha...e blank....
  reg [`BYTE_range] KernelStatus, ReadStatus, EraseStatus, ProgramStatus, BuffEnhancedProgramStatus,
                     LockStatus, ConfigStatus, BufferStatus,BlankCheckStatus,ProgramBufferStatus,
                    SuspendStatus, ResumeStatus, ClearSRStatus, ProtectRegStatus;
 

reg [`BYTE_range] status=`Free_pes;

//address latching in read operation
  always @(negedge LE_N) if (W_N==`HIGH)  begin
        if (KernelStatus == `READY && ConfigReg_man.isASynchronous) 
                @(posedge LE_N) begin
                               if (L_N)

                                AddrBusIn = A;                // AddressBus has been Latched

                end
  end
 
always @(negedge LE_N) if (W_N==`HIGH) begin :latching_a
        if (KernelStatus == `READY) begin
            if(ConfigReg_man.isSynchronous)
            fork
            
            begin : L_Address

                @(posedge LE_N) if (L_N) begin
                                   
                                   AddrBusIn = A;                // AddressBus has been Latched
                                    -> Debug.x0;
                                   disable K_Address;

                end
            end    
               
               begin : K_Address

               @(posedge CLK) begin

                                AddrBusIn = A;                // AddressBus has been Latched
                                    -> Debug.x1;
                                disable L_Address;
                end
               end 
            join

  end
end


 always @(negedge WE_N) begin
        if (KernelStatus==`READY) 
                @(posedge WE_N) begin
                         if(OE_N==`HIGH)
                                AddrBusIn = A;                // AddressBus has been Latched

                end                
  end

  integer i;
  integer n_block;
  
   // Wait Driver 
  time timeWaitDriver,timeWaitDriverZ;

  reg PB_init=0;
  reg P_init=0;
  reg BP_init=0;
  reg Prog_init=0;

  always @(PB_init,P_init,BP_init) begin
    Prog_init=(PB_init ||P_init || BP_init);
  end

  wire [`BYTE_range] AccessTime;
  
  // ****************
  // 
  // Modules Istances
  //
  // ****************

 DataErrorModule   DataError_man();              // Check for errors on UserData.h

 CUIdecoder1       ReadArray_Command  (DQ[`LOW_range], "Read Array                         ", `RD_cmd, (Kernel.Ready && !Prog_init),        Info);
 CUIdecoder1       ReadSR_Command     (DQ[`LOW_range], "Read Status Register               ", `RSR_cmd, !Prog_init,        Info),
                   ReadSign_Command   (DQ[`LOW_range], "Read Electronic Signature          ", `RSIG_cmd, (Kernel.Ready && !Prog_init),       Info),
                   ReadCFI_Command    (DQ[`LOW_range], "Read CFI                           ", `RCFI_cmd, (Kernel.Ready && !Prog_init),      Info),
                   Program_Command    (DQ[`LOW_range], "Program                            ", `PG_cmd,   (Kernel.Ready && !Prog_init),      Info),

                   ProgramBuffer_Command    (DQ[`LOW_range], "Program Buffer                     ", `PB_cmd, (Kernel.Ready && !Prog_init),        Info),
                   ProgramReg_Command (DQ[`LOW_range], "Protection Register Program        ", `PRREG_cmd,  (Kernel.Ready && !Prog_init),    Info),
                   Resume_Command     (DQ[`LOW_range], "Resume                             ", `PER_cmd,  (Kernel.Ready && Kernel.Suspended),      Info),
                  BlockErase_Command  (DQ[`LOW_range], "Block Erase                        ", `BLKEE_cmd,(Kernel.Ready && !Prog_init),      Info),
                   ClearSR_Command    (DQ[`LOW_range], "Clear Status Register              ", `CLRSR_cmd, (Kernel.Ready && !Prog_init),     Info),

                     BlankCheck_Command  (DQ[`LOW_range], "Blank Check                        ", `BLNKCHK_cmd, (Kernel.Ready && !Prog_init),   Info),
                   BuffEnhactoryProgram_Command (DQ[`LOW_range], "Buffer Enh.Factory Program  [Setup]", `BuffEnhProgram_cmd,(Kernel.Ready && !Prog_init), Info);
 
 
 CUIdecoder_Busy1  Suspend_Command    (DQ[`LOW_range], "Suspend ",   `PES_cmd, !Kernel.Ready, Info);

 CUIdecoder2        BlockLock_Command (DQ[`LOW_range], "Block Lock                 ", `BL_cmd,       `BLconfirm_cmd, (Kernel.Ready && !Prog_init),    Info),
                  BlockUnlock_Command (DQ[`LOW_range], "Block UnLock               ", `BUL_cmd,      `BULconfirm_cmd, (Kernel.Ready && !Prog_init),   Info),
                BlockLockDown_Command (DQ[`LOW_range], "Block Lock-Down            ", `BLD_cmd,      `BLDconfirm_cmd, (Kernel.Ready && !Prog_init),   Info),
                 SetConfigReg_Command (DQ[`LOW_range], "Set Configuration Register ", `SCR_cmd,      `SCRconfirm_cmd, (Kernel.Ready && !Prog_init),   Info);

 KernelModule            Kernel            (VDD, VDDQ, VPP, Info);
 ReadModule              Read_man          (DataBusIn, AddrBusIn, Kernel.ioVoltOK, Info);
 OutputBufferModule      OutputBuffer_man  (DataBusIn, DataBurst, DQ, OE_N);
 StatusRegModule         SR_man            (Info);
 MemoryModule            Memory_man        (Info);
 ProgramModule           Program_man       (AddrBusIn, DQ, Kernel.progVoltOK, Kernel.progHighVoltOK, Info);

BuffEnhancedFactProgramModule BuffEnhancedFactProgram_man(AddrBusIn, DQ, Kernel.progVoltOK, Kernel.progHighVoltOK, Info);
 ProtectRegModule        ProtectReg_man    (AddrBusIn, DQ, Kernel.progVoltOK, Info);
 EraseModule             Erase_man         (AddrBusIn, DQ, Kernel.eraseVoltOK, Kernel.progHighVoltOK, Info);


BlankCheckModule        BlankCheck_man    (AddrBusIn, DQ, Kernel.eraseVoltOK, Kernel.progHighVoltOK, Info);

 BlockLockModule         BlockLock_man     (AddrBusIn, WP_N, RP_N, Info);
 ProgramBufferModule   ProgramBuffer_man (AddrBusIn, DQ, Kernel.progVoltOK, Info);
 SignatureModule         Signature_man     ();        // , `FALSE);
 CFIqueryModule          CFIquery_man      ();        // , `TRUE);
 ConfigRegModule         ConfigReg_man     (AddrBusIn,Info);                              // implements the Configuration Register
 //BurstModule             Burst_man         (A, DataBurst, isWait, CLK, CLOCK, L_N, G_N,W_N, Info);
 BurstModule             Burst_man         (AddrBusIn, DataBurst, isWait, CLK, CLOCK, L_N, G_N,W_N, Info);
 
 BankLib                 BankLib_man       ();
 TimingDataModule        TimingData_man    ();
 TimingLibModule         TimingLib_man     (A,DQ,W_N,G_N,E_N,L_N,WP_N,K,VPP);

 DebugModule Debug();

  initial begin

    $timeformat(-9, 0, " ns", 12);               // Format time displays to screen
    -> Kernel.ResetEvent;                        // Reset Device 
    KernelStatus = `BUSY;                        // Device is Busy
    $display ("[%t]  --- Device is Busy  (start up time) --- ", $time);
    #TimingData_man.tVDHPH KernelStatus = `READY;              // End of Start-Up Time
    $display ("[%t]  --- Device is Ready (end of start-up time) --- ", $time);

    AddrBusIn = `ADDRBUS_dim'hZ;

    wait_ = 1'hZ;
    CLOCK = 1'b0;
  end

  // Recognize command input
  always @(negedge WE_N) begin
        if (KernelStatus==`READY) 
                @(posedge WE_N) begin

                           -> Kernel.CUIcommandEvent;               // new command has been written into Kernel.

                end
  end

  // Check error
  always @(Kernel.CUIcommandEvent) begin : Timeout  
    #3
     -> Kernel.ErrorEvent;                      
       disable Verify;                                 
  end
 
  // Verify command issued 
  always @(Kernel.CUIcommandEvent) begin : Verify   
    @(Kernel.VerifyEvent)  

       disable Timeout;          
  end
 
  // Default to Read Array command
  always @(negedge OE_N) begin
    if (OE_N == `LOW && (ConfigReg_man.isASynchronous)) begin
    if (L_N==0) AddrBusIn=A;
      #1 
          -> Kernel.ReadEvent;
    end      
  end

 // Page Read
 always @(A) begin
  
if ((OE_N == `LOW) &&  (A !== `ADDRBUS_dim'hZ) && (A !== `ADDRBUS_dim'hx) && (ConfigReg_man.isASynchronous))  begin
        AddrBusIn = A;
         #0 -> Kernel.ReadEvent;
          
    end 
 end 
 

  // Reset the Kernel
 always @(negedge RP_N) begin 
        -> Kernel.ResetEvent;
        if (Info) $display ("[%t]  Device has been reset ", $time);
        KernelStatus = `BUSY;
        @(posedge RP_N) KernelStatus = `READY;
 end


  always @(posedge Kernel.CommandDecode1[`RD_cmd] or negedge Kernel.CommandDecode1[`RD_cmd]) if (KernelStatus==`READY)  begin                        // Read Array
    Read_man.SetMode(`ReadArray_bus, ReadStatus);
    Kernel.Report(`RD_cmd, 8'hXX, ReadStatus);
    #1 -> Kernel.CompleteEvent;
  end

  always @(posedge Kernel.CommandDecode1[`RSR_cmd] or negedge Kernel.CommandDecode1[`RSR_cmd]) if (KernelStatus==`READY) begin                       // Read Status Register
    Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
    Kernel.Report(`RSR_cmd, 8'hXX, ReadStatus); 
    #1 -> Kernel.CompleteEvent;
  end


  always @(posedge Kernel.CommandDecode1[`RSIG_cmd] or negedge Kernel.CommandDecode1[`RSIG_cmd]) if (KernelStatus==`READY )  begin                      // Read Electronic Signature
    Read_man.SetMode(`ReadSignature_bus, ReadStatus);
    Kernel.Report(`RSIG_cmd, 8'hXX, ReadStatus);
    #1 -> Kernel.CompleteEvent;
  end

  always @(posedge Kernel.CommandDecode1[`RCFI_cmd] or negedge Kernel.CommandDecode1[`RCFI_cmd]) if (KernelStatus==`READY)  begin                      // Read CFI 
    Read_man.SetMode(`ReadCFI_bus, ReadStatus);
    Kernel.Report(`RCFI_cmd, 8'hXX, ReadStatus); 
    #1 -> Kernel.CompleteEvent;
  end
  
  always @(posedge Kernel.CommandDecode1[`PG_cmd] or negedge Kernel.CommandDecode1[`PG_cmd]) if (KernelStatus==`READY) begin                     // Program
    P_init=1;
    @Kernel.CUIcommandEvent
    #1 -> Kernel.VerifyEvent;
    Program_man.Program(ProgramStatus);
    Kernel.Report(`PG_cmd, 8'hXX, ProgramStatus);
    -> Kernel.CompleteEvent;
    P_init=0;
  end
  

  always @(posedge Kernel.CommandDecode1[`PRREG_cmd] or negedge Kernel.CommandDecode1[`PRREG_cmd]) if (KernelStatus==`READY)  begin                      // Protection Register Program
    @Kernel.CUIcommandEvent 
       #1 -> Kernel.VerifyEvent;
    ProtectReg_man.Program(ProtectRegStatus);
    Kernel.Report(`PRREG_cmd, 8'hXX, ProtectRegStatus);
       -> Kernel.CompleteEvent;
  end
  
  always @(posedge Kernel.CommandDecode1[`PES_cmd] or negedge Kernel.CommandDecode1[`PES_cmd]) if (KernelStatus==`READY) begin                       // Suspend
    if (Program_man.IsBusy(1'bX))
      Program_man.Suspend(SuspendStatus);
    else if (ProgramBuffer_man.IsBusy(1'bX))
     ProgramBuffer_man.Suspend(SuspendStatus); 
     else if (Erase_man.IsBusy(1'bX))
      Erase_man.Suspend(SuspendStatus);
    -> Kernel.CompleteEvent;
  end
  
 
  always @(posedge Kernel.CommandDecode1[`PER_cmd] or negedge Kernel.CommandDecode1[`PER_cmd]) if (KernelStatus==`READY) begin                       // Program/Erase Resume
    ResumeStatus = `NoError_msg;
    if (Program_man.IsSuspended(1'bX)) begin
      Program_man.Resume(ProgramStatus);
      Kernel.Report(`PG_cmd, 8'hXX, ProgramStatus);
    end
    else if (ProgramBuffer_man.IsSuspended(1'bX)) begin 
      ProgramBuffer_man.Resume(BufferStatus);
      Kernel.Report(`PB_cmd, 8'hXX, BufferStatus);
    end
    else if (Erase_man.IsSuspended(1'bX)) begin
      Erase_man.Resume(EraseStatus);
      Kernel.Report(`BLKEE_cmd, 8'hXX, EraseStatus);
    end
    else
     ResumeStatus = `NoSusp_msg;
     Kernel.Report(`PER_cmd, 8'hXX, ResumeStatus);
    -> Kernel.CompleteEvent;

end

  always @(posedge Kernel.CommandDecode1[`BLKEE_cmd] or negedge Kernel.CommandDecode1[`BLKEE_cmd]) if (KernelStatus==`READY) begin                    // Block Erase
         Read_man.SetMode(`ReadStatusReg_bus,ReadStatus);
         EraseStatus=`NoError_msg;
         @Kernel.CUIcommandEvent
              Erase_man.checkConfirm(EraseStatus);
              #1 -> Kernel.VerifyEvent;
              if (EraseStatus != `NoError_msg)
                Kernel.Report(`BLKEE_cmd, `BLKEEconfirm_cmd, EraseStatus);
              else
                begin
                        Erase_man.BlockErase(EraseStatus);
                        Kernel.Report(`BLKEE_cmd, `BLKEEconfirm_cmd , EraseStatus);
                        -> Kernel.CompleteEvent;
                end        
  end
  
  always @(posedge Kernel.CommandDecode1[`CLRSR_cmd] or negedge Kernel.CommandDecode1[`CLRSR_cmd]) if (KernelStatus==`READY)  begin                    // Clear Status Register
    SR_man.Clear(ClearSRStatus);
    Kernel.Report(`CLRSR_cmd, 8'hXX, ClearSRStatus); 
    #1 -> Kernel.CompleteEvent;
  end


//aggiunta ************************************************
// PB Fast Program Commands
  always @(posedge Kernel.CommandDecode1[`PB_cmd] or negedge Kernel.CommandDecode1[`PB_cmd]) if (KernelStatus==`READY)  begin                       // Write to Program and Buffer 
    ProgramBufferStatus = `NoError_msg;
    PB_init=1;
    Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
    @Kernel.CUIcommandEvent 
    ProgramBuffer_man.SetCount(ProgramBufferStatus);
        #1 -> Kernel.VerifyEvent;
    
    if (ProgramBufferStatus == `NoError_msg) begin
      for (i=1; i <= ProgramBuffer_man.GetCount(1'bX); i=i+1) begin : GetData
        @Kernel.CUIcommandEvent
           #1;

            ProgramBuffer_man.Load(ProgramBufferStatus);
          #1 -> Kernel.VerifyEvent;
          if (ProgramBufferStatus != `NoError_msg)
              disable GetData;
      end
      @Kernel.CUIcommandEvent
        if (DQ[`BYTE_range] != `PBcfm_cmd)
          ProgramBufferStatus = `CmdSeq_msg;
        else begin
           #1 -> Kernel.VerifyEvent;
          ProgramBuffer_man.Program(ProgramBufferStatus);
        end
    end
    Kernel.Report(`PB_cmd, 8'hXX, ProgramBufferStatus);
    ->Kernel.CompleteEvent;
    PB_init=0;
  end
//*************************************************************************

  always @(posedge Kernel.CommandDecode2[{`BL_cmd,`BLconfirm_cmd}] or negedge Kernel.CommandDecode2[{`BL_cmd,`BLconfirm_cmd}]) if (KernelStatus==`READY)  begin      // Block Lock
    BlockLock_man.Lock(LockStatus);
    Kernel.Report(`BL_cmd, `BLconfirm_cmd, LockStatus);
    -> Kernel.CompleteEvent;
  end

  always @(posedge Kernel.CommandDecode2[{`BUL_cmd,`BULconfirm_cmd}] or negedge Kernel.CommandDecode2[{`BUL_cmd,`BULconfirm_cmd}]) if (KernelStatus==`READY) begin    // Block UnLock
   BlockLock_man.UnLock(LockStatus);
   Kernel.Report(`BUL_cmd,`BULconfirm_cmd, LockStatus);
   -> Kernel.CompleteEvent;
  end

  always @(posedge Kernel.CommandDecode2[{`BLD_cmd,`BLDconfirm_cmd}] or negedge Kernel.CommandDecode2[{`BLD_cmd,`BLDconfirm_cmd}]) if (KernelStatus==`READY)  begin    // Block Lock-Down
   BlockLock_man.LockDown(LockStatus);
   Kernel.Report(`BLD_cmd,`BLDconfirm_cmd, LockStatus);
   -> Kernel.CompleteEvent;
  end

  always @(posedge Kernel.CommandDecode2[{`SCR_cmd,`SCRconfirm_cmd}] or negedge Kernel.CommandDecode2[{`SCR_cmd,`SCRconfirm_cmd}]) if (KernelStatus==`READY) begin    // Set Configuration Register
   ConfigReg_man.putConfigReg(ConfigStatus);
   Kernel.Report(`SCR_cmd,`SCRconfirm_cmd, ConfigStatus);
   -> Kernel.CompleteEvent;
  end


 // BC
  always @(posedge Kernel.CommandDecode1[`BLNKCHK_cmd] or negedge Kernel.CommandDecode1[`BLNKCHK_cmd]) if (KernelStatus==`READY)  begin                    // Blank Check
         BlankCheckStatus=`NoError_msg;
         Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
           @Kernel.CUIcommandEvent
              BlankCheck_man.checkConfirm(BlankCheckStatus);
              #1 -> Kernel.VerifyEvent;
              
              if (BlankCheckStatus != `NoError_msg) begin
                
                Kernel.Report(`BLNKCHK_cmd, `BLNKCHKconfirm_cmd, BlankCheckStatus);
              end else
                begin
                        BlankCheck_man.BlankCheck(BlankCheckStatus);
                        Kernel.Report(`BLNKCHK_cmd, `BLNKCHKconfirm_cmd, BlankCheckStatus);
                end        
         -> Kernel.CompleteEvent;

  end
  // BEFP 
  always @(posedge Kernel.CommandDecode1[`BuffEnhProgram_cmd] or negedge Kernel.CommandDecode1[`BuffEnhProgram_cmd]) if (KernelStatus==`READY)  begin    // Buffer Enhanced Factory Program: Setup Phase
        Read_man.SetMode(`ReadStatusReg_bus, ReadStatus);
        BP_init=1;
        @Kernel.CUIcommandEvent
                 #1 -> Kernel.VerifyEvent;
        if (Kernel.Suspended | !Kernel.Ready)
                BuffEnhancedProgramStatus = `SuspCmd_msg;
        else begin 
             if (DQ[`LOW_range]!=`BuffEnhProgramCfrm_cmd)  
                   BuffEnhancedProgramStatus=`CmdSeq_msg;     
             else begin 
                  if (Info) $display("[%t]  Command Issued: Buffer Enh.Factory Program  [Confirm]",$time);
           
                  BuffEnhancedFactProgram_man.Setup(BuffEnhancedProgramStatus);
                  if (BuffEnhancedProgramStatus == `NoError_msg) begin 
                     while (BuffEnhancedProgramStatus == `NoError_msg)  begin               // Loop Program - Enhanced Factory Program: Program Phase
                     if (Info) $display("[%t]  Enhanced Factory Program -> Load Phase",$time);
               
                        while (BuffEnhancedProgramStatus == `NoError_msg ) begin               // Loop Load - Enhanced Factory Program: Load Phase
                          @Kernel.CUIcommandEvent
                          #1 -> Kernel.VerifyEvent;
                               BuffEnhancedFactProgram_man.Load(BuffEnhancedProgramStatus);
                        end 
                     if (BuffEnhancedProgramStatus==`ProgramPHASE_BEFP_msg) begin
                             BuffEnhancedFactProgram_man.Program(BuffEnhancedProgramStatus);
                     end        
                  end 
                  BuffEnhancedFactProgram_man.Exit(BuffEnhancedProgramStatus);
             end
         end 
   if (BuffEnhancedProgramStatus == `ExitPHASE_BEFP_msg) 
                        BuffEnhancedProgramStatus = `NoError_msg;
   end 
   Kernel.Report(`BuffEnhProgram_cmd,`BuffEnhProgramCfrm_cmd, BuffEnhancedProgramStatus);
   -> Kernel.CompleteEvent;
   BP_init=0;
 end
 
 

//***********************************************************

 // Decode Delays for Page Mode Reads

//******************************************************

  // Page mode
  always 
    begin :nopage
     @(A[`ADDRBUS_dim - 1:4])
     disable page;          

     OutputBuffer_man.SetValid(TimingData_man.tAVQV);
    end 
    
  // Page mode
  always
    begin :page
     @(A[3:0]) //pagina di 16 words
     OutputBuffer_man.SetValid(TimingData_man.tAVQV1);
    end 


  // Output Buffer delays 

  always @(negedge E_N) begin
    OutputBuffer_man.SetX(TimingData_man.tELQX);
    OutputBuffer_man.SetValid(TimingData_man.tELQV);

  end
  
  always @(negedge G_N) begin
  #0;
    OutputBuffer_man.SetX(TimingData_man.tGLQX);
    OutputBuffer_man.SetValid(TimingData_man.tGLQV);

  end
   
  always @(posedge CLK) begin
                CLOCK = !CLOCK;
  end
 
  always @(negedge CLK) begin
                 CLOCK = !CLOCK;
  end



reg waiting=0;

always @(posedge G_N) begin
waiting=0;

end



 always @(CLK) begin

    if ((!G_N) && (CE_N == `LOW) && (ConfigReg_man.isSynchronous) && (CLK)) begin

   if (ConfigReg_man.isWaitBeforeActive && Burst_man.firstEOWL && waiting) begin
              OutputBuffer_man.SetX(TimingData_man.tKHQX);
                @(posedge (CLK))
                 OutputBuffer_man.SetX(TimingData_man.tKHQX);
                OutputBuffer_man.SetValid(TimingData_man.tKHQV);
                waiting=0;

   end else begin
                
                OutputBuffer_man.SetX(TimingData_man.tKHQX);
                OutputBuffer_man.SetValid(TimingData_man.tKHQV);
               
end
               
    end 
  end 

always @(negedge L_N) if(W_N==`HIGH)begin
 if (ConfigReg_man.isSynchronous && CE_N==`LOW) begin
 OutputBuffer_man.SetValid(TimingData_man.tLLQV);
 

end
end


  always @(RP_N) begin
    if (RP_N == `HIGH)
 OutputBuffer_man.SetValid(TimingData_man.tPHWL);
  end
 
  always @(posedge CE_N) begin
    OutputBuffer_man.SetZ(TimingData_man.tEHQZ);
  end
  
  always @(posedge G_N) begin
    OutputBuffer_man.SetZ(TimingData_man.tGHQZ);
    OutputBuffer_man.SetZ(TimingData_man.tGHTZ);

  end



////////////////////////////////
  always @(CE_N) begin 
    if (CE_N == `LOW && W_N==`HIGH && G_N == `LOW) begin 
        if (ConfigReg_man.isSynchronous)  
        wait_ = #TimingData_man.tELTV ConfigReg_man.isWaitPolActiveHigh;
        else wait_ = #TimingData_man.tELTV !ConfigReg_man.isWaitPolActiveHigh;
       end
    else
    wait_ = #TimingData_man.tEHTZ 1'hZ;
  end 

always @(G_N) begin 
    if (G_N == `LOW && CE_N == `LOW && W_N==`HIGH) begin
         if (ConfigReg_man.isSynchronous) begin
            wait_ = #TimingData_man.tGLTV ConfigReg_man.isWaitPolActiveHigh;
         end else begin
             wait_ = #TimingData_man.tGLTV !ConfigReg_man.isWaitPolActiveHigh;
         end
    end
            else begin if (G_N == `HIGH )  
        wait_ = #TimingData_man.tGHTZ 1'hZ;
        
        disable Burst_man.pollingBurst;
       end
  end 




always @(isWait) begin

if ((CE_N == `LOW) && (G_N == `LOW) && ConfigReg_man.isSynchronous ) begin
    
        if (CLK) begin
            if (isWait == `LOW ) begin
                 if(!Burst_man.nWait) begin
                    wait_ = #TimingData_man.tKHTV ConfigReg_man.isWaitPolActiveHigh;
                    waiting=1;
                 end
                else wait_ = #TimingData_man.tKHTX ConfigReg_man.isWaitPolActiveHigh;
            end else begin
                 if (!Burst_man.nWait) wait_ = #TimingData_man.tKHTV !ConfigReg_man.isWaitPolActiveHigh;
                           else wait_ = #TimingData_man.tKHTX !ConfigReg_man.isWaitPolActiveHigh;
            end        

        end else 
       
        fork 
        
        begin
        @(posedge(CLK)) 
            
            if (isWait == `LOW) begin
                 if(!Burst_man.nWait) begin
                    wait_ = #TimingData_man.tKHTV ConfigReg_man.isWaitPolActiveHigh;
                    waiting=1;
                 end
                else wait_ = #TimingData_man.tKHTX ConfigReg_man.isWaitPolActiveHigh;
            end else begin
                 if (!Burst_man.nWait) wait_ = #TimingData_man.tKHTV !ConfigReg_man.isWaitPolActiveHigh;
                           else wait_ = #TimingData_man.tKHTX !ConfigReg_man.isWaitPolActiveHigh;
            end 
        end
       

        begin
        
        @(isWait)
        
         if (CLK) begin
            if (isWait == `LOW) begin
                 if(!Burst_man.nWait) begin
                    wait_ = #TimingData_man.tKHTV ConfigReg_man.isWaitPolActiveHigh;
                    waiting=1;
                 end   

                else wait_ = #TimingData_man.tKHTX ConfigReg_man.isWaitPolActiveHigh;
            end else begin
                 if (!Burst_man.nWait) wait_ = #TimingData_man.tKHTV !ConfigReg_man.isWaitPolActiveHigh;
                           else wait_ = #TimingData_man.tKHTX !ConfigReg_man.isWaitPolActiveHigh;
            end

        end
        end   

       join
        
    
    end else if  (G_N == `HIGH && isWait == `HIGH && W_N==`HIGH)
    $display("%t --- WARNING --- WAIT should be deasserted but OE# is not yet LOW. Please check the timings!",$time);
 end 


endmodule



// *********************************
//
// Burst module :
//
//      manage the Read Burst operation
//
// *********************************

module BurstModule(address, data, ISWAIT, CLK, CLOCK, L_N, G_N, W_N, Info);
  input [`ADDRBUS_range] address;
  output [`WORD_range] data;
  reg [`WORD_range] data;

  input CLK;
  input CLOCK;
  input L_N;
  input G_N;
   input W_N;
  output ISWAIT;
  input Info;

  reg [`ADDRBUS_range] Start_address, Sync_address,new_address;
  reg EnableBurst, isValidData, IsNowWait, endSingleSynchronous;
  reg [2:0] incLSBaddress, incMSBaddress, temp_address;
  
  wire isSingleSynchronous = (Read_man.Mode != `ReadArray_bus) ? `TRUE : `FALSE;
  
  integer WaitState,nWait,nRead,xLatency;
  //aggiunta per il calcolo degli nwait
  integer boundary,offset;
  reg firstEOWL;

  initial begin                 // constructor sequence
    Start_address = `ADDRBUS_dim'h000000;
    EnableBurst = `FALSE;
    endSingleSynchronous = `FALSE;
    data = 16'hZZ;
    nWait = 0;
    IsNowWait = `FALSE;
    isValidData = `FALSE;
    xLatency=0;
    nRead=0;
    WaitState=0;
    firstEOWL=0;
  end

always @(G_N) if (G_N==`TRUE) begin
  IsNowWait = `FALSE;
    isValidData = `FALSE;
    EnableBurst = `FALSE;
    endSingleSynchronous = `FALSE;
    data = 16'hZZ;
    nWait = 0;
    xLatency=0;
    nRead=0;
    WaitState=0;
    firstEOWL=0;
end


always @(isValidData) begin
   case (isValidData)

   1: if (!ConfigReg_man.isWaitBeforeActive) begin
              
              IsNowWait = `TRUE;
         end 

   0: begin if (!ConfigReg_man.isWaitBeforeActive) 
              IsNowWait = `FALSE;

              
         end
         
   endcase
end 


assign ISWAIT = (IsNowWait) ? `TRUE : `FALSE;


always @(negedge L_N) if(W_N==`HIGH) begin  : pollingBurst
fork  : pollingBurst

begin: L_lacthing
      @(posedge L_N) if (ConfigReg_man.isSynchronous) begin 
                #1;
                Start_address = address;
                Sync_address =  address;
                firstEOWL=0;
                disable K_lacthing;
                
             @(posedge CLK) begin

                case(ConfigReg_man.BurstLength)

                0: begin
                    boundary =16;
                    offset = address[3:0];
                   end
                
                16:begin
                    boundary =16;
                    offset = address[3:0];
                   end

                8: begin
                    boundary =8;
                    offset = address[2:0];
                   end


                4:begin
                    boundary =4;
                    offset = address[1:0];
                   end
               endcase
                
                xLatency = ConfigReg_man.Xlatency;
                 WaitState = xLatency - (boundary - offset);
                 
                 if (WaitState < 0) WaitState =0; 
                nWait = 0;
                EnableBurst = `TRUE;
                data = 16'hXX;
                nRead = 0;
                isValidData = `FALSE;
                endSingleSynchronous=`FALSE;
                disable pollingBurst;
        end
        end
           else EnableBurst = `FALSE;
 end

begin: K_lacthing
@(posedge CLK) if (ConfigReg_man.isSynchronous && L_N==`LOW) begin 
                #1;
                Start_address = address;
                Sync_address =  address;
                firstEOWL=0;
                disable L_lacthing;

                @(posedge CLK) begin

                case(ConfigReg_man.BurstLength)

                0: begin
                    boundary =16;
                    offset = address[3:0];
                   end
                
                16:begin
                    boundary =16;
                    offset = address[3:0];
                   end

                8: begin
                    boundary =8;
                    offset = address[2:0];
                   end


                4:begin
                    boundary =4;
                    offset = address[1:0];
                   end
               endcase
                
                xLatency = ConfigReg_man.Xlatency;
                 WaitState = xLatency - (boundary - offset); //
                 if (WaitState < 0) WaitState =0; 
                nWait = 0;
                EnableBurst = `TRUE;
                data = 16'hXX;
                nRead = 0;
                isValidData = `FALSE;
                endSingleSynchronous=`FALSE;
                disable pollingBurst;
           end
           end
           else EnableBurst = `FALSE;
 end
join

end


always @(posedge (CLK)) if(G_N==`LOW) begin 
  if (EnableBurst) begin
  if (xLatency == 2 && ConfigReg_man.isWaitBeforeActive)
    IsNowWait = `TRUE;

    if (xLatency == 1) begin
        isValidData = `TRUE;
        if (offset == 4'd15 && ConfigReg_man.isWaitBeforeActive && WaitState!=0 && (ConfigReg_man.isNoWrapBurst || ConfigReg_man.BurstLength == 5'd00)) begin
                IsNowWait = `FALSE;

    end

  
    end
    if (xLatency) xLatency = xLatency - 1; //vuol dire se xLatency e' >1 o diverso da zero????
    end     
end
    
always @(nRead) begin 
if (isSingleSynchronous && nRead>=1) begin //End of SingleBurstRead???
                endSingleSynchronous=`TRUE;
                isValidData = `FALSE;
                end
if((offset + nRead) == 4'd15 && ConfigReg_man.isWaitBeforeActive && WaitState!=0 && (ConfigReg_man.isNoWrapBurst || ConfigReg_man.BurstLength == 5'd00)) begin
IsNowWait = `FALSE;

end
                
end


always @(CLK) begin 
   
   if (EnableBurst) begin
      
      if (!xLatency) begin // burst is ongoing(after xLatency)
          
          if (!G_N) begin 
              
              if (nWait || endSingleSynchronous) data = `DATABUS_dim'hXXXX; //Wait State;
          
              else begin  // Read is Possible!
                        // -- \\ 
                        case (Read_man.Mode)
                           `ReadArray_bus       : begin  
                                                  data = Memory_man.Get(Sync_address);
                                                  @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: Memory[%h]=%h\n",$time,Sync_address,data);
                                                  end
                           `ReadCFI_bus         : begin 
                                                  data = CFIquery_man.Get(Sync_address);
                                                  @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: CFIMemory[%h]=%h\n",$time,Sync_address,data);
                                                  end
                           `ReadSignature_bus   : begin 
                                                  data = Signature_man.Get(Sync_address);
                                                  @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: Electronic Signature[%h]=%h\n",$time,Sync_address,data);
                                                  end
                           `ReadStatusReg_bus   : begin 
                                                  data = SR_man.SR;
                                                  @(posedge (CLK)) if (Info && !G_N) $write("[%t]  Burst Read: StatusRegister: %b\n",$time,data[`BYTE_range]);
                                                  end
                             default             : $display("[%t]  !!!Model Error: Read mode not recognized!!!", $time);
                        endcase
                        // -- \\     
               end 
             if((CLK)) begin
             
               if (!nWait) // Wait State??? if no calculate next address
                           begin
                           
                           new_address = Sync_address + 1;
                           nRead = nRead + 1;
                          
                           end
               if (!isSingleSynchronous) begin

                  // Calcultate Address for Sequential and Wrap Burst 
                  if ((ConfigReg_man.BurstLength != 5'd00) && ConfigReg_man.isWrapBurst)  begin      
                        case (ConfigReg_man.BurstLength_bit) 
                           3'd2: new_address = {Sync_address[`ADDRBUS_dim - 1 : 2], new_address[1:0] };
                           3'd3: new_address = {Sync_address[`ADDRBUS_dim - 1 : 3], new_address[2:0] };
                           3'd4: new_address = {Sync_address[`ADDRBUS_dim - 1 : 4], new_address[3:0] };
                        endcase
                  end 
             
                  // Calculate Next Wait State
                  if (ConfigReg_man.isNoWrapBurst || (ConfigReg_man.BurstLength == 5'd00) )  //Calculate WAIT STATE
                  if ((new_address[3:0]==4'd0) && (Sync_address[3:0] == 4'd15)) begin
                  
                       if(!ConfigReg_man.isWaitBeforeActive)  begin

                           if (nWait<WaitState && !firstEOWL) begin
                                         nWait = nWait+1; // Another Wait State???
                                         isValidData = `FALSE;
                           end else begin 
                                         nWait = 0;       // end of wait state
                                         Sync_address = new_address;
                                         isValidData = `TRUE;
                                         firstEOWL =1;

                          end

                      end else begin
                          if (nWait<WaitState-1 && !firstEOWL ) begin
                                         nWait = nWait+1; // Another Wait State???
                                         IsNowWait = `FALSE;
                            end else begin
                                         nWait = 0;       // end of wait state
                                         Sync_address = new_address;
                                         IsNowWait = `TRUE;
                                         firstEOWL =1;

                           end
                    end 

               end  
              if (!nWait) 
                  if ((nRead<ConfigReg_man.BurstLength) || (ConfigReg_man.BurstLength==5'd00) // Read Data is Over Burst Lenght???
                        && !endSingleSynchronous) // end of SingleSinchronous Burst Read ???
                        Sync_address = new_address;
          end  // !isSyn
         
         end //aggiunta
         
        end //G_N
        end // XLatency
      end //Enable Burst
end


endmodule // end Burst Module 


// Erase Manager
// manage the erase functionality

module BlankCheckModule(address, data, progVoltOK, progHighVoltOK,Info);

  input [`WORD_range] data;
  input [`ADDRBUS_range] address;
  input progVoltOK, progHighVoltOK;
  input Info;
  
  event ErrorCheckEvent, CompleteEvent;

  reg [`BYTE_range] Status;
  reg [`ADDRBUS_range] hold_address;
  reg [`BLOCKADDR_range] hold_block;


  reg Busy;
  integer i;
  time startTime, delayTime, Erase_time;

  initial begin                   // constructor sequence             
    Busy       = `FALSE;                                                    
    Erase_time = `MainBlockErase_time; //modificato
    delayTime  =  Erase_time;                                      
  end         
  
  always @(progVoltOK,progHighVoltOK,address) begin
     if (progHighVoltOK) 
                    if (BankLib_man.isMainBlock(address)) Erase_time=`FastMainBlockErase_time;
                                             else  Erase_time=`FastParameterBlockErase_time;
            else 
                    if (BankLib_man.isMainBlock(address)) Erase_time=`MainBlockErase_time;
                                             else  Erase_time=`ParameterBlockErase_time;
  end 
     

  function IsBusy;                // boolean function primitive       
  input obbl;                     // all functions require a parameter
    IsBusy = Busy;                // return Boolean value             
  endfunction                                                         
                                                                      


  // *********************
  //
  // Task checkConfirm :
  //    check confirm code
  //
  // *********************

  task checkConfirm;
  
  output  [`BYTE_range] outStatus;
  
  reg     [`BYTE_range] outStatus;

  begin

    if (data == `BLNKCHKconfirm_cmd) outStatus = `NoError_msg;
    
       else outStatus = `WrongBlankCheckConfirm_msg;
  end
  endtask




 
  // ****************
  //
  // Task Blank Check
  // 
  // ****************
  
  task BlankCheck;
  
  output  [`BYTE_range] outStatus;
  
  reg     [`BYTE_range] outStatus;

  integer hold_block;
  reg [`ADDRBUS_range] hold_address;

  begin
        hold_address = address;
        
        hold_block = BankLib_man.getBlock(hold_address);
        
        if (BankLib_man.isMainBlock(address)) begin
                  // Main Block
                  delayTime = `MainBlankCheck_time;
            end else  // Parameter Block
                   -> ErrorCheckEvent;
                   disable Operation;

        fork
                begin: Operation
                        Busy = `TRUE;
                        startTime = $time;
                        -> ErrorCheckEvent; 
                        #delayTime         
                                Memory_man.BlockBlankCheck(hold_block, Status);
                        -> CompleteEvent;
                end
                @CompleteEvent
                        disable Operation; 
        join
        outStatus = Status;
        Busy = `FALSE;
  end
  endtask



  always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
    if (!progVoltOK)
      Status = `InvVDD_msg;
   if (BankLib_man.isParameterBlock(address)) begin
      
       Status = `WrongBlankCheckBlock;
   end    
    if (Status != `NoError_msg) begin
      ->CompleteEvent;
      disable ErrorCheck;
    end
    else
      fork : ErrorCheck
        @(negedge progVoltOK) Status = `InvVDD_msg;
        @(Status) -> CompleteEvent;
        @(CompleteEvent) disable ErrorCheck;
      join
  end

endmodule  //end module Erase

// *********************************
//
// Program Buffer module :
//
//      program buffer functionality
//
// *********************************

module ProgramBufferModule(address,data,voltOK,Info);
  input [`ADDRBUS_range] address;
  input [`WORD_range] data;
  input  voltOK, Info;
  event ErrorCheckEvent, CompleteEvent, WatchAddressEvent;
  reg [`BYTE_range] Status;
  reg [`DATABUS_dim-1:0] Count;

  reg [`WORD_range] bufferData [`ProgramBuffer_range];

  reg [`ADDRBUS_range] AddressLatched, startAddress,newAddress;
  reg Busy, Suspended, Empty;
  time startTime, delayTime;
  integer i;
  
  initial begin                 // constructor sequence             
    Busy = `FALSE;                                                  
    Suspended = `FALSE;
    Empty = `TRUE;
    delayTime = `ProgramBuffer_time;                                        
  end                                                               
                                                                    
  function IsBusy;              // boolean function primitive       
  input obbl;               // all functions require a parameter
    IsBusy = Busy;              // return Boolean value             
  endfunction                                                       
                                                                    
  function IsSuspended;         // boolean function primitive       
  input obbl;               // all functions require a parameter
    IsSuspended = Suspended;    // return Boolean value             
  endfunction                                                       
                                                                    
  function IsAddrSuspended;     // boolean function primitive       
  input [`ADDRBUS_range] addr;
    IsAddrSuspended = (Suspended && ((addr >= startAddress) && (addr < (startAddress + Count))));
  endfunction


  function [`DATABUS_dim-1:0] GetCount;
  input required;
    GetCount = Count;
  endfunction

  task SetCount;                // sets the number of writes
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin
      outStatus = `NoError_msg;
      AddressLatched = address;
      Count = data + 1;
      

      if (Count > `ProgramBuffer_dim)
        outStatus = `BuffSize_msg;
       else if (BankLib_man.getBlock(AddressLatched) != BankLib_man.getBlock(AddressLatched + Count - 1))
      
       outStatus = `BlkBuffer_msg;
      else
        -> WatchAddressEvent;
    end
  endtask
  
  task Suspend;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin
      delayTime = delayTime - ($time - startTime);
      #`ProgramSuspendLatency_time;
      outStatus = `NoError_msg;
      Status = `Suspend_msg;
      Suspended = `TRUE;
      -> CompleteEvent;
    end
  endtask

  task Resume;
  output [`BYTE_range] Status;
    begin
      Suspended = `FALSE;
      Program(Status);
    end
  endtask

  task Load;
  output [`BYTE_range] Status;
      begin
      if (Empty) begin
        startAddress = address;
        if (Info) $display("[%t]  Buffer start address: %h",$time,startAddress);
        Empty = `FALSE;
        
         end
      bufferData[address[`ProgramBuffer_addrRange]] = data;

    end
  endtask
//  task Load;
//  output [`BYTE_range] Status;
//      begin
//      startAddress = address;
//      if (Empty) begin
//        if (Info) $display("[%t]  Buffer start address: %h",$time,startAddress);
//        Empty = `FALSE;
//      end
//      else begin
//          if (Info) $display("[%t] Buffer loading... %h with %h",$time,startAddress,data);
//      end    
//      bufferData[address[`ProgramBuffer_addrRange]] = data;
//
//    end
//  endtask
  
  task Program;
  output [`BYTE_range] outStatus;
  reg [`BYTE_range] outStatus;
    begin

      fork
        begin : Operation
           Busy = `TRUE;
 
          startTime = $time;
          -> ErrorCheckEvent;
          -> WatchAddressEvent; // disable address watch
          #delayTime
          for (i = startAddress;i < ( Count + startAddress); i = i + 1) begin
          Memory_man.Program(bufferData[i[`ProgramBuffer_addrRange]],i,Status);

          end
          delayTime = `ProgramBuffer_time;
          -> CompleteEvent;
        end
        @CompleteEvent
          disable Operation;

      join
      if(!Suspended)
      for (i = 0; i < `ProgramBuffer_dim; i = i + 1) begin
      bufferData[i] =16'hFFFF;
      end 
      Empty = `TRUE;
      outStatus = Status;
      Busy = `FALSE;
    end
  endtask

  always @(ErrorCheckEvent) begin
    Status = `NoError_msg;
     if (BlockLock_man.IsLocked(AddressLatched))
      Status = `BlockLock_msg;
     else if (Suspended)
      Status = `SuspAcc_msg;
     else if (!voltOK)
      Status = `InvVDD_msg;

    if (Status != `NoError_msg)
      ->CompleteEvent;
    else
      fork : ErrorCheck
        @(negedge voltOK) Status = `InvVDD_msg;
        @(Status) -> CompleteEvent;
        @(CompleteEvent) disable ErrorCheck;
      join
  end
  
  always @(WatchAddressEvent) fork : AddressWatch
    while (`TRUE)
      @address
          if (BankLib_man.getBlock(address) != BankLib_man.getBlock(AddressLatched)) begin
          Status = `AddrTog_msg;
          -> CompleteEvent;
        end
    @WatchAddressEvent
      disable AddressWatch;
  join
  
endmodule

module DebugModule ();

event x0;
event x1;
event x2;

endmodule
